594 lines
15 KiB
PHP
594 lines
15 KiB
PHP
<?php
|
|
/** User authentication / flag stuff */
|
|
$auth = array(
|
|
'level' => false,
|
|
'flags' => false,
|
|
'allow' => false,
|
|
'deny' => false,
|
|
'guest' => true,
|
|
);
|
|
|
|
$levelorder = array(
|
|
1 => 'janitor',
|
|
10 => 'mod',
|
|
20 => 'manager',
|
|
50 => 'admin'
|
|
);
|
|
|
|
$levelorderf = array(
|
|
'janitor' => 1,
|
|
'mod' => 10,
|
|
'manager' => 20,
|
|
'admin' => 50
|
|
);
|
|
|
|
if (!defined('SQLLOGMOD')) {
|
|
define("SQLLOGMOD", "mod_users");
|
|
define('PASS_TIMEOUT', 1800);
|
|
define('LOGIN_FAIL_HOURLY', 5);
|
|
}
|
|
|
|
function csrf_tag() {
|
|
if (isset($_COOKIE['_tkn'])) {
|
|
return '<input type="hidden" value="' . htmlspecialchars($_COOKIE['_tkn'], ENT_QUOTES) . '" name="_tkn">';
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function csrf_attr() {
|
|
if (isset($_COOKIE['_tkn'])) {
|
|
return 'data-tkn="' . htmlspecialchars($_COOKIE['_tkn'], ENT_QUOTES) . '"';
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function auth_encrypt($data) {
|
|
$key = file_get_contents('/www/keys/2015_enc.key');
|
|
|
|
if (!$key) {
|
|
return false;
|
|
}
|
|
|
|
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
|
|
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
|
|
|
|
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
|
|
|
|
if ($encrypted === false) {
|
|
return false;
|
|
}
|
|
|
|
return $iv . $encrypted;
|
|
}
|
|
|
|
function auth_decrypt($data) {
|
|
$key = file_get_contents('/www/keys/2015_enc.key');
|
|
|
|
if (!$key) {
|
|
return false;
|
|
}
|
|
|
|
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
|
|
$iv_dec = substr($data, 0, $iv_size);
|
|
|
|
$data = substr($data, $iv_size);
|
|
|
|
$data = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv_dec);
|
|
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
|
|
return rtrim($data, "\0");
|
|
}
|
|
|
|
function verify_one_time_pwd($username, $otp) {
|
|
if (!$otp) {
|
|
return false;
|
|
}
|
|
|
|
$query = "SELECT auth_secret FROM mod_users WHERE username = '%s' LIMIT 1";
|
|
|
|
$res = mysql_global_call($query, $username);
|
|
|
|
if (!$res) {
|
|
return false;
|
|
}
|
|
|
|
$enc_secret = mysql_fetch_row($res)[0];
|
|
|
|
if (!$enc_secret) {
|
|
return false;
|
|
}
|
|
|
|
require_once 'lib/GoogleAuthenticator.php';
|
|
|
|
$ga = new PHPGangsta_GoogleAuthenticator();
|
|
|
|
$dec_secret = auth_decrypt($enc_secret);
|
|
|
|
if ($dec_secret === false) {
|
|
return false;
|
|
}
|
|
|
|
if ($ga->verifyCode($dec_secret, $otp, 2)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns a hash containing implicit levels for the current authed level
|
|
* ex: will return array('janitor' => true, 'mod' => true)
|
|
* if the current level is 'mod'
|
|
*/
|
|
function get_level_map($level = null) {
|
|
global $auth, $levelorderf;
|
|
|
|
$map = array();
|
|
|
|
if (!$level) {
|
|
$level = $auth['level'];
|
|
}
|
|
|
|
if (!$level) {
|
|
return $map;
|
|
}
|
|
|
|
$level_value = (int)$levelorderf[$level];
|
|
|
|
foreach ($levelorderf as $k => $v) {
|
|
if ($v <= $level_value) {
|
|
$map[$k] = true;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
function has_level( $level = 'mod', $board = false )
|
|
{
|
|
if( is_local_auth() ) return YES;
|
|
|
|
global $auth, $levelorder, $levelorderf;
|
|
static $ourlevel = -1;
|
|
|
|
|
|
//if( !$board && defined( 'BOARD_DIR' ) ) $board = BOARD_DIR;
|
|
//if( !access_board($board) ) return false;
|
|
if( $ourlevel < 0 ) $ourlevel = $levelorderf[$auth['level']];
|
|
|
|
if (!isset($levelorderf[$level])) {
|
|
return false;
|
|
}
|
|
|
|
if( $levelorderf[$level] <= $ourlevel ) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function has_flag( $flag, $board = false )
|
|
{
|
|
if( is_local_auth() ) return YES;
|
|
|
|
global $auth;
|
|
if( $auth['guest'] ) return false;
|
|
|
|
if( !access_board( $board ) ) return false;
|
|
if( in_array( $flag, $auth['flags'] ) ) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function access_board( $board )
|
|
{
|
|
if( is_local_auth() ) return YES;
|
|
|
|
global $auth;
|
|
|
|
if( $auth['guest'] ) return false;
|
|
|
|
$can_do = false;
|
|
|
|
// See if we have access to this board or all
|
|
if( in_array( 'all', $auth['allow'] ) || in_array( $board, $auth['allow'] ) ) $can_do = true;
|
|
|
|
// Are we denied on this board?
|
|
if( $board && in_array( $board, $auth['deny'] ) ) $can_do = false;
|
|
|
|
// If we're not using a board, are we denied for no-board stuff?
|
|
if( !$board && in_array( 'noboard', $auth['deny'] ) ) $can_do = false;
|
|
|
|
return $can_do;
|
|
}
|
|
|
|
function is_user()
|
|
{
|
|
if( is_local_auth() ) return YES;
|
|
|
|
global $auth;
|
|
if( $auth['guest'] ) return false;
|
|
if( $auth['level'] ) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function auth_user($skip_agreement = false) {
|
|
global $auth;
|
|
|
|
$user = $_COOKIE['4chan_auser'];
|
|
$pass = $_COOKIE['apass'];
|
|
|
|
if( !$user || !$pass ) return false;
|
|
|
|
$query = mysql_global_call("SELECT * FROM `%s` WHERE `username` = '%s' LIMIT 1", SQLLOGMOD, $user);
|
|
|
|
if (!mysql_num_rows($query)) {
|
|
return false;
|
|
}
|
|
|
|
$fetch = mysql_fetch_assoc($query);
|
|
|
|
$admin_salt = file_get_contents('/www/keys/2014_admin.salt');
|
|
|
|
if (!$admin_salt) {
|
|
die('Internal Server Error (s0)');
|
|
}
|
|
|
|
$hashed_admin_password = hash('sha256', $fetch['username'] . $fetch['password'] . $admin_salt);
|
|
|
|
if ($hashed_admin_password !== $pass) {
|
|
return false;
|
|
}
|
|
|
|
if ($fetch['password_expired'] == 1) {
|
|
die('Your password has expired; check IRC for instructions on changing it.');
|
|
}
|
|
|
|
if (!$skip_agreement) {
|
|
if ($fetch['signed_agreement'] == 0 && basename($_SERVER['SELF_PATH']) !== 'agreement.php' && basename($_SERVER['SELF_PATH']) !== 'agreement_genkey.php') {
|
|
die('You must agree to the 4chan Volunteer Moderator Agreement in order to access moderation tools. Please check your e-mail for more information.');
|
|
}
|
|
}
|
|
|
|
$auth['level'] = $fetch['level'];
|
|
$auth['flags'] = explode( ',', $fetch['flags'] );
|
|
$auth['allow'] = explode( ',', $fetch['allow'] );
|
|
$auth['deny'] = explode( ',', $fetch['deny'] );
|
|
$auth['guest'] = false;
|
|
|
|
$flags = array();
|
|
|
|
if( has_level( 'admin' ) ) {
|
|
$flags['forcedanonname'] = 2;
|
|
}
|
|
|
|
if( has_level( 'manager' ) || has_flag( 'html' ) ) {
|
|
$flags['html'] = 1;
|
|
}
|
|
|
|
$flags = array_flip( $flags );
|
|
$flags = implode( ',', $flags );
|
|
|
|
$ips_array = json_decode($fetch['ips'], true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
die('Database Error (1-0)');
|
|
}
|
|
|
|
$ips_array[$_SERVER['REMOTE_ADDR']] = $_SERVER['REQUEST_TIME'];
|
|
|
|
if (count($ips_array) > 512) {
|
|
asort($ips_array);
|
|
array_shift($ips_array);
|
|
}
|
|
|
|
$ips_array = json_encode($ips_array);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
die('Database Error (1-1)');
|
|
}
|
|
|
|
if (mb_strlen($_SERVER['HTTP_USER_AGENT']) > 128) {
|
|
$ua = mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 128);
|
|
}
|
|
else {
|
|
$ua = $_SERVER['HTTP_USER_AGENT'];
|
|
}
|
|
|
|
mysql_global_call("UPDATE `%s` SET ips = '$ips_array', last_ua = '%s' WHERE id = %d LIMIT 1", SQLLOGMOD, $ua, $fetch['id']);
|
|
|
|
return true;
|
|
}
|
|
// OLD auth
|
|
/*
|
|
function auth_user( $login = false )
|
|
{
|
|
global $auth;
|
|
|
|
if( $login ) {
|
|
$user = $_POST['userlogin'];
|
|
$pass = $_POST['passlogin'];
|
|
} else {
|
|
$user = $_COOKIE['4chan_auser'];
|
|
$pass = $_COOKIE['4chan_apass'];
|
|
}
|
|
|
|
if( !$user || !$pass ) return false;
|
|
|
|
$query = mysql_global_call( "SELECT * FROM `%s` WHERE `username` = '%s' LIMIT 1", SQLLOGMOD, $user );
|
|
if( !mysql_num_rows( $query ) ) return false;
|
|
$fetch = mysql_fetch_assoc( $query );
|
|
|
|
if( $fetch['password_expired'] == 1 ) {
|
|
die( 'Your password has expired; check IRC for instructions on changing it.' );
|
|
}
|
|
|
|
if ($login) {
|
|
if( !password_verify($pass, $fetch['password'])) return false;
|
|
|
|
$pass = $fetch['password'];
|
|
} else {
|
|
if ($pass != $fetch['password']) return false;
|
|
}
|
|
|
|
$auth['level'] = $fetch['level'];
|
|
$auth['flags'] = explode( ',', $fetch['flags'] );
|
|
$auth['allow'] = explode( ',', $fetch['allow'] );
|
|
$auth['deny'] = explode( ',', $fetch['deny'] );
|
|
$auth['guest'] = false;
|
|
|
|
$flags = array();
|
|
|
|
if( has_level( 'admin' ) && $user == 'moot' ) {
|
|
$flags['forcedanonname'] = 2;
|
|
}
|
|
|
|
if( has_level( 'manager' ) || has_flag( 'html' ) ) {
|
|
$flags['html'] = 1;
|
|
}
|
|
|
|
$flags = array_flip( $flags );
|
|
$flags = implode( ',', $flags );
|
|
|
|
$ips_array = json_decode($fetch['ips'], true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
die('Database Error (1-0)');
|
|
}
|
|
|
|
$ips_array[$_SERVER['REMOTE_ADDR']] = $_SERVER['REQUEST_TIME'];
|
|
$ips_array = json_encode($ips_array);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
die('Database Error (1-1)');
|
|
}
|
|
|
|
if ($login) {
|
|
$login_query = ", last_login = now()";
|
|
}
|
|
else {
|
|
if (!isset($_COOKIE['apass'])) {
|
|
return false;
|
|
}
|
|
|
|
$admin_salt = file_get_contents('/www/keys/2014_admin.salt');
|
|
|
|
if (!$admin_salt) {
|
|
die('Internal Server Error (s0)');
|
|
}
|
|
|
|
$hashed_admin_cookie = $_COOKIE['apass'];
|
|
$hashed_admin_password = hash('sha256', $fetch['username'] . $fetch['password'] . $admin_salt);
|
|
|
|
if ($hashed_admin_password !== $hashed_admin_cookie) {
|
|
return false;
|
|
}
|
|
|
|
$login_query = '';
|
|
}
|
|
|
|
mysql_global_do("UPDATE `%s` SET ips = '$ips_array' $login_query WHERE id = %d", SQLLOGMOD, $fetch['id']);
|
|
|
|
if( !isset( $_COOKIE['4chan_auser'] ) || !isset( $_COOKIE['4chan_apass'] ) ) {
|
|
if( strstr( $_SERVER["HTTP_HOST"], ".4chan.org" ) ) {
|
|
setcookie( "4chan_auser", $user, time() + 30 * 24 * 3600, "/", ".4chan.org", true, true );
|
|
setcookie( "4chan_apass", $pass, time() + 30 * 24 * 3600, "/", ".4chan.org", true, true );
|
|
setcookie( "4chan_aflags", $flags, time() + 30 * 24 * 3600, "/", ".4chan.org", true );
|
|
|
|
$jspath = $auth['level'] == 'janitor' ? JANITOR_JS_PATH : ADMIN_JS_PATH;
|
|
if( !isset( $_COOKIE['extra_path'] ) || !in_array( $_COOKIE['extra_path'], array(JANITOR_JS_PATH, ADMIN_JS_PATH) ) ) {
|
|
setcookie( 'extra_path', $jspath, time() + ( 30 * 24 * 3600 ), '/', '.4chan.org' );
|
|
}
|
|
} elseif( strstr( $_SERVER["HTTP_HOST"], ".4channel.org" ) ) {
|
|
setcookie( "4chan_auser", $user, time() + 30 * 24 * 3600, "/", ".4channel.org", true, true );
|
|
setcookie( "4chan_apass", $pass, time() + 30 * 24 * 3600, "/", ".4channel.org", true, true );
|
|
} else {
|
|
die( 'Not 4chan.org' );
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
*/
|
|
function is_local_auth()
|
|
{
|
|
if (!isset($_SERVER['REMOTE_ADDR'])) {
|
|
return true;
|
|
}
|
|
|
|
// local rpc can do anything
|
|
$longip = ip2long( $_SERVER['REMOTE_ADDR'] );
|
|
|
|
if(
|
|
cidrtest( $longip, "10.0.0.0/24" ) ||
|
|
cidrtest( $longip, "204.152.204.0/24" ) ||
|
|
cidrtest( $longip, "127.0.0.0/24" )
|
|
) {
|
|
return YES;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function can_delete( $resno )
|
|
{
|
|
if( !has_level( 'janitor' ) ) return false;
|
|
if( has_level( 'janitor' ) && access_board( BOARD_DIR ) ) return true;
|
|
//if( !access_board(BOARD_DIR) ) return false;
|
|
|
|
$query = mysql_global_do( "SELECT COUNT(*) from reports WHERE board='%s' AND no=%d AND cat=2", BOARD_DIR, $resno );
|
|
$illegal_count = mysql_result( $query, 0, 0 );
|
|
mysql_free_result( $query );
|
|
|
|
return $illegal_count >= 3;
|
|
}
|
|
|
|
function start_auth_captcha($use_alt_captcha = false)
|
|
{
|
|
if (valid_captcha_bypass() !== true) {
|
|
if ($use_alt_captcha) {
|
|
start_recaptcha_verify_alt();
|
|
}
|
|
else {
|
|
start_recaptcha_verify();
|
|
}
|
|
}
|
|
}
|
|
|
|
function clear_pass_cookies() {
|
|
setcookie('pass_id', null, 1, '/', 'sys.4chan.org', true, true);
|
|
setcookie('pass_id', null, 1, '/', '.4chan.org', true, true);
|
|
setcookie('pass_enabled', null, 1, '/', '.4chan.org');
|
|
}
|
|
|
|
function valid_captcha_bypass()
|
|
{
|
|
global $captcha_bypass, $passid, $rangeban_bypass;
|
|
|
|
$captcha_bypass = false;
|
|
$rangeban_bypass = false;
|
|
|
|
$passid = '';
|
|
|
|
if (is_local_auth() || has_level('janitor')) {
|
|
$captcha_bypass = true;
|
|
$rangeban_bypass = true;
|
|
return true;
|
|
}
|
|
|
|
if (CAPTCHA != 1) {
|
|
$captcha_bypass = true;
|
|
}
|
|
|
|
$time = $_SERVER['REQUEST_TIME'];
|
|
$host = $_SERVER['REMOTE_ADDR'];
|
|
|
|
// check for 4chan pass
|
|
$pass_cookie = isset( $_COOKIE['pass_id'] ) ? $_COOKIE['pass_id'] : '';
|
|
|
|
if (strlen($pass_cookie) == 10) {
|
|
setcookie('pass_id', '0', 1, '/', '.4chan.org', true, true);
|
|
setcookie('pass_enabled', '0', 1, '/', '.4chan.org');
|
|
error(S_PASSFORMATCHANGED);
|
|
}
|
|
|
|
if ($pass_cookie) {
|
|
$pass_parts = explode('.', $pass_cookie);
|
|
|
|
$pass_user = $pass_parts[0];
|
|
$pass_session = $pass_parts[1];
|
|
|
|
if (!$pass_user || !$pass_session) {
|
|
error(S_INVALIDPASS);
|
|
}
|
|
|
|
// The column is case insensitive but all passes should be uppercase to avoid ban bypassing exploits.
|
|
$pass_user = strtoupper($pass_user);
|
|
|
|
$admin_salt = file_get_contents('/www/keys/2014_admin.salt');
|
|
|
|
if (!$admin_salt) {
|
|
die('Internal Server Error (s0)');
|
|
}
|
|
|
|
$passq = mysql_global_call("SELECT user_hash, session_id, last_ip, last_used, last_country, status, pending_id, UNIX_TIMESTAMP(expiration_date) as expiration_date FROM pass_users WHERE pin != '' AND user_hash = '%s'", $pass_user);
|
|
|
|
if( !$passq ) error( S_INVALIDPASS );
|
|
|
|
$res = mysql_fetch_assoc($passq);
|
|
|
|
if (!$res || !$res['session_id']) {
|
|
clear_pass_cookies();
|
|
error(S_INVALIDPASS);
|
|
}
|
|
|
|
$hashed_pass_session = substr(hash('sha256', $res['session_id'] . $admin_salt), 0, 32);
|
|
|
|
if ($hashed_pass_session !== $pass_session) {
|
|
clear_pass_cookies();
|
|
error(S_INVALIDPASS);
|
|
}
|
|
|
|
if ((int)$res['expiration_date'] <= $time) {
|
|
clear_pass_cookies();
|
|
error(sprintf(S_PASSEXPIRED, $res['pending_id']));
|
|
}
|
|
|
|
if ($res['status'] != 0) {
|
|
clear_pass_cookies();
|
|
error(S_PASSDISABLED);
|
|
}
|
|
|
|
$lastused = strtotime( $res['last_used'] );
|
|
$lastip_mask = ip2long( $res['last_ip'] ) & ( ~255 );
|
|
$ip_mask = ip2long( $host ) & ( ~255 );
|
|
|
|
if( $lastip_mask !== 0 && ( $time - $lastused ) < PASS_TIMEOUT && $lastip_mask != $ip_mask ) {
|
|
|
|
// old strict code, above is to match last octet
|
|
//if( ( $time - $lastused ) < PASS_TIMEOUT && $res['last_ip'] != $host && $res['last_ip'] != '0.0.0.0' ) {
|
|
clear_pass_cookies();
|
|
error( S_PASSINUSE );
|
|
}
|
|
|
|
$update_country = '';
|
|
|
|
if ($res['last_ip'] !== $host) {
|
|
$geo_data = GeoIP2::get_country($host);
|
|
|
|
if ($geo_data && isset($geo_data['country_code'])) {
|
|
$country_code = $geo_data['country_code'];
|
|
}
|
|
else {
|
|
$country_code = 'XX';
|
|
}
|
|
|
|
$update_country = ", last_country = '" . mysql_real_escape_string($country_code) . "'";
|
|
}
|
|
|
|
$passid = $pass_user;
|
|
|
|
$captcha_bypass = true;
|
|
$rangeban_bypass = true;
|
|
|
|
mysql_global_call( "UPDATE pass_users SET last_used = NOW(), last_ip = '%s' $update_country WHERE user_hash = '%s' AND status = 0 LIMIT 1", $host, $res['user_hash'], $host );
|
|
}
|
|
|
|
return $captcha_bypass;
|
|
}
|
|
|
|
// some code paths might think current admin name is 4chan_auser cookie
|
|
// when that's not set (e.g. local requests), assert out here
|
|
function validate_admin_cookies()
|
|
{
|
|
if (!$_COOKIE['4chan_auser']) {
|
|
error('Internal error (internal request missing name)');
|
|
}
|
|
}
|