4chan/lib/auth-test.php
2025-04-17 14:46:47 -05:00

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)');
}
}