auth-test.php
1 <?php 2 /** User authentication / flag stuff */ 3 $auth = array( 4 'level' => false, 5 'flags' => false, 6 'allow' => false, 7 'deny' => false, 8 'guest' => true, 9 ); 10 11 $levelorder = array( 12 1 => 'janitor', 13 10 => 'mod', 14 20 => 'manager', 15 50 => 'admin' 16 ); 17 18 $levelorderf = array( 19 'janitor' => 1, 20 'mod' => 10, 21 'manager' => 20, 22 'admin' => 50 23 ); 24 25 if (!defined('SQLLOGMOD')) { 26 define("SQLLOGMOD", "mod_users"); 27 define('PASS_TIMEOUT', 1800); 28 define('LOGIN_FAIL_HOURLY', 5); 29 } 30 31 function csrf_tag() { 32 if (isset($_COOKIE['_tkn'])) { 33 return '<input type="hidden" value="' . htmlspecialchars($_COOKIE['_tkn'], ENT_QUOTES) . '" name="_tkn">'; 34 } 35 else { 36 return ''; 37 } 38 } 39 40 function csrf_attr() { 41 if (isset($_COOKIE['_tkn'])) { 42 return 'data-tkn="' . htmlspecialchars($_COOKIE['_tkn'], ENT_QUOTES) . '"'; 43 } 44 else { 45 return ''; 46 } 47 } 48 49 function auth_encrypt($data) { 50 $key = file_get_contents('/www/keys/2015_enc.key'); 51 52 if (!$key) { 53 return false; 54 } 55 56 $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); 57 $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); 58 59 $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv); 60 61 if ($encrypted === false) { 62 return false; 63 } 64 65 return $iv . $encrypted; 66 } 67 68 function auth_decrypt($data) { 69 $key = file_get_contents('/www/keys/2015_enc.key'); 70 71 if (!$key) { 72 return false; 73 } 74 75 $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); 76 $iv_dec = substr($data, 0, $iv_size); 77 78 $data = substr($data, $iv_size); 79 80 $data = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv_dec); 81 82 if ($data === false) { 83 return false; 84 } 85 86 return rtrim($data, "\0"); 87 } 88 89 function verify_one_time_pwd($username, $otp) { 90 if (!$otp) { 91 return false; 92 } 93 94 $query = "SELECT auth_secret FROM mod_users WHERE username = '%s' LIMIT 1"; 95 96 $res = mysql_global_call($query, $username); 97 98 if (!$res) { 99 return false; 100 } 101 102 $enc_secret = mysql_fetch_row($res)[0]; 103 104 if (!$enc_secret) { 105 return false; 106 } 107 108 require_once 'lib/GoogleAuthenticator.php'; 109 110 $ga = new PHPGangsta_GoogleAuthenticator(); 111 112 $dec_secret = auth_decrypt($enc_secret); 113 114 if ($dec_secret === false) { 115 return false; 116 } 117 118 if ($ga->verifyCode($dec_secret, $otp, 2)) { 119 return true; 120 } 121 122 return false; 123 } 124 125 /** 126 * Returns a hash containing implicit levels for the current authed level 127 * ex: will return array('janitor' => true, 'mod' => true) 128 * if the current level is 'mod' 129 */ 130 function get_level_map($level = null) { 131 global $auth, $levelorderf; 132 133 $map = array(); 134 135 if (!$level) { 136 $level = $auth['level']; 137 } 138 139 if (!$level) { 140 return $map; 141 } 142 143 $level_value = (int)$levelorderf[$level]; 144 145 foreach ($levelorderf as $k => $v) { 146 if ($v <= $level_value) { 147 $map[$k] = true; 148 } 149 } 150 151 return $map; 152 } 153 154 function has_level( $level = 'mod', $board = false ) 155 { 156 if( is_local_auth() ) return YES; 157 158 global $auth, $levelorder, $levelorderf; 159 static $ourlevel = -1; 160 161 162 //if( !$board && defined( 'BOARD_DIR' ) ) $board = BOARD_DIR; 163 //if( !access_board($board) ) return false; 164 if( $ourlevel < 0 ) $ourlevel = $levelorderf[$auth['level']]; 165 166 if (!isset($levelorderf[$level])) { 167 return false; 168 } 169 170 if( $levelorderf[$level] <= $ourlevel ) return true; 171 172 return false; 173 } 174 175 function has_flag( $flag, $board = false ) 176 { 177 if( is_local_auth() ) return YES; 178 179 global $auth; 180 if( $auth['guest'] ) return false; 181 182 if( !access_board( $board ) ) return false; 183 if( in_array( $flag, $auth['flags'] ) ) return true; 184 185 return false; 186 } 187 188 function access_board( $board ) 189 { 190 if( is_local_auth() ) return YES; 191 192 global $auth; 193 194 if( $auth['guest'] ) return false; 195 196 $can_do = false; 197 198 // See if we have access to this board or all 199 if( in_array( 'all', $auth['allow'] ) || in_array( $board, $auth['allow'] ) ) $can_do = true; 200 201 // Are we denied on this board? 202 if( $board && in_array( $board, $auth['deny'] ) ) $can_do = false; 203 204 // If we're not using a board, are we denied for no-board stuff? 205 if( !$board && in_array( 'noboard', $auth['deny'] ) ) $can_do = false; 206 207 return $can_do; 208 } 209 210 function is_user() 211 { 212 if( is_local_auth() ) return YES; 213 214 global $auth; 215 if( $auth['guest'] ) return false; 216 if( $auth['level'] ) return true; 217 218 return false; 219 } 220 221 function auth_user($skip_agreement = false) { 222 global $auth; 223 224 $user = $_COOKIE['4chan_auser']; 225 $pass = $_COOKIE['apass']; 226 227 if( !$user || !$pass ) return false; 228 229 $query = mysql_global_call("SELECT * FROM `%s` WHERE `username` = '%s' LIMIT 1", SQLLOGMOD, $user); 230 231 if (!mysql_num_rows($query)) { 232 return false; 233 } 234 235 $fetch = mysql_fetch_assoc($query); 236 237 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 238 239 if (!$admin_salt) { 240 die('Internal Server Error (s0)'); 241 } 242 243 $hashed_admin_password = hash('sha256', $fetch['username'] . $fetch['password'] . $admin_salt); 244 245 if ($hashed_admin_password !== $pass) { 246 return false; 247 } 248 249 if ($fetch['password_expired'] == 1) { 250 die('Your password has expired; check IRC for instructions on changing it.'); 251 } 252 253 if (!$skip_agreement) { 254 if ($fetch['signed_agreement'] == 0 && basename($_SERVER['SELF_PATH']) !== 'agreement.php' && basename($_SERVER['SELF_PATH']) !== 'agreement_genkey.php') { 255 die('You must agree to the 4chan Volunteer Moderator Agreement in order to access moderation tools. Please check your e-mail for more information.'); 256 } 257 } 258 259 $auth['level'] = $fetch['level']; 260 $auth['flags'] = explode( ',', $fetch['flags'] ); 261 $auth['allow'] = explode( ',', $fetch['allow'] ); 262 $auth['deny'] = explode( ',', $fetch['deny'] ); 263 $auth['guest'] = false; 264 265 $flags = array(); 266 267 if( has_level( 'admin' ) ) { 268 $flags['forcedanonname'] = 2; 269 } 270 271 if( has_level( 'manager' ) || has_flag( 'html' ) ) { 272 $flags['html'] = 1; 273 } 274 275 $flags = array_flip( $flags ); 276 $flags = implode( ',', $flags ); 277 278 $ips_array = json_decode($fetch['ips'], true); 279 280 if (json_last_error() !== JSON_ERROR_NONE) { 281 die('Database Error (1-0)'); 282 } 283 284 $ips_array[$_SERVER['REMOTE_ADDR']] = $_SERVER['REQUEST_TIME']; 285 286 if (count($ips_array) > 512) { 287 asort($ips_array); 288 array_shift($ips_array); 289 } 290 291 $ips_array = json_encode($ips_array); 292 293 if (json_last_error() !== JSON_ERROR_NONE) { 294 die('Database Error (1-1)'); 295 } 296 297 if (mb_strlen($_SERVER['HTTP_USER_AGENT']) > 128) { 298 $ua = mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 128); 299 } 300 else { 301 $ua = $_SERVER['HTTP_USER_AGENT']; 302 } 303 304 mysql_global_call("UPDATE `%s` SET ips = '$ips_array', last_ua = '%s' WHERE id = %d LIMIT 1", SQLLOGMOD, $ua, $fetch['id']); 305 306 return true; 307 } 308 // OLD auth 309 /* 310 function auth_user( $login = false ) 311 { 312 global $auth; 313 314 if( $login ) { 315 $user = $_POST['userlogin']; 316 $pass = $_POST['passlogin']; 317 } else { 318 $user = $_COOKIE['4chan_auser']; 319 $pass = $_COOKIE['4chan_apass']; 320 } 321 322 if( !$user || !$pass ) return false; 323 324 $query = mysql_global_call( "SELECT * FROM `%s` WHERE `username` = '%s' LIMIT 1", SQLLOGMOD, $user ); 325 if( !mysql_num_rows( $query ) ) return false; 326 $fetch = mysql_fetch_assoc( $query ); 327 328 if( $fetch['password_expired'] == 1 ) { 329 die( 'Your password has expired; check IRC for instructions on changing it.' ); 330 } 331 332 if ($login) { 333 if( !password_verify($pass, $fetch['password'])) return false; 334 335 $pass = $fetch['password']; 336 } else { 337 if ($pass != $fetch['password']) return false; 338 } 339 340 $auth['level'] = $fetch['level']; 341 $auth['flags'] = explode( ',', $fetch['flags'] ); 342 $auth['allow'] = explode( ',', $fetch['allow'] ); 343 $auth['deny'] = explode( ',', $fetch['deny'] ); 344 $auth['guest'] = false; 345 346 $flags = array(); 347 348 if( has_level( 'admin' ) && $user == 'moot' ) { 349 $flags['forcedanonname'] = 2; 350 } 351 352 if( has_level( 'manager' ) || has_flag( 'html' ) ) { 353 $flags['html'] = 1; 354 } 355 356 $flags = array_flip( $flags ); 357 $flags = implode( ',', $flags ); 358 359 $ips_array = json_decode($fetch['ips'], true); 360 361 if (json_last_error() !== JSON_ERROR_NONE) { 362 die('Database Error (1-0)'); 363 } 364 365 $ips_array[$_SERVER['REMOTE_ADDR']] = $_SERVER['REQUEST_TIME']; 366 $ips_array = json_encode($ips_array); 367 368 if (json_last_error() !== JSON_ERROR_NONE) { 369 die('Database Error (1-1)'); 370 } 371 372 if ($login) { 373 $login_query = ", last_login = now()"; 374 } 375 else { 376 if (!isset($_COOKIE['apass'])) { 377 return false; 378 } 379 380 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 381 382 if (!$admin_salt) { 383 die('Internal Server Error (s0)'); 384 } 385 386 $hashed_admin_cookie = $_COOKIE['apass']; 387 $hashed_admin_password = hash('sha256', $fetch['username'] . $fetch['password'] . $admin_salt); 388 389 if ($hashed_admin_password !== $hashed_admin_cookie) { 390 return false; 391 } 392 393 $login_query = ''; 394 } 395 396 mysql_global_do("UPDATE `%s` SET ips = '$ips_array' $login_query WHERE id = %d", SQLLOGMOD, $fetch['id']); 397 398 if( !isset( $_COOKIE['4chan_auser'] ) || !isset( $_COOKIE['4chan_apass'] ) ) { 399 if( strstr( $_SERVER["HTTP_HOST"], ".4chan.org" ) ) { 400 setcookie( "4chan_auser", $user, time() + 30 * 24 * 3600, "/", ".4chan.org", true, true ); 401 setcookie( "4chan_apass", $pass, time() + 30 * 24 * 3600, "/", ".4chan.org", true, true ); 402 setcookie( "4chan_aflags", $flags, time() + 30 * 24 * 3600, "/", ".4chan.org", true ); 403 404 $jspath = $auth['level'] == 'janitor' ? JANITOR_JS_PATH : ADMIN_JS_PATH; 405 if( !isset( $_COOKIE['extra_path'] ) || !in_array( $_COOKIE['extra_path'], array(JANITOR_JS_PATH, ADMIN_JS_PATH) ) ) { 406 setcookie( 'extra_path', $jspath, time() + ( 30 * 24 * 3600 ), '/', '.4chan.org' ); 407 } 408 } elseif( strstr( $_SERVER["HTTP_HOST"], ".4channel.org" ) ) { 409 setcookie( "4chan_auser", $user, time() + 30 * 24 * 3600, "/", ".4channel.org", true, true ); 410 setcookie( "4chan_apass", $pass, time() + 30 * 24 * 3600, "/", ".4channel.org", true, true ); 411 } else { 412 die( 'Not 4chan.org' ); 413 } 414 415 } 416 417 return true; 418 } 419 */ 420 function is_local_auth() 421 { 422 if (!isset($_SERVER['REMOTE_ADDR'])) { 423 return true; 424 } 425 426 // local rpc can do anything 427 $longip = ip2long( $_SERVER['REMOTE_ADDR'] ); 428 429 if( 430 cidrtest( $longip, "10.0.0.0/24" ) || 431 cidrtest( $longip, "204.152.204.0/24" ) || 432 cidrtest( $longip, "127.0.0.0/24" ) 433 ) { 434 return YES; 435 } 436 437 return false; 438 } 439 440 function can_delete( $resno ) 441 { 442 if( !has_level( 'janitor' ) ) return false; 443 if( has_level( 'janitor' ) && access_board( BOARD_DIR ) ) return true; 444 //if( !access_board(BOARD_DIR) ) return false; 445 446 $query = mysql_global_do( "SELECT COUNT(*) from reports WHERE board='%s' AND no=%d AND cat=2", BOARD_DIR, $resno ); 447 $illegal_count = mysql_result( $query, 0, 0 ); 448 mysql_free_result( $query ); 449 450 return $illegal_count >= 3; 451 } 452 453 function start_auth_captcha($use_alt_captcha = false) 454 { 455 if (valid_captcha_bypass() !== true) { 456 if ($use_alt_captcha) { 457 start_recaptcha_verify_alt(); 458 } 459 else { 460 start_recaptcha_verify(); 461 } 462 } 463 } 464 465 function clear_pass_cookies() { 466 setcookie('pass_id', null, 1, '/', 'sys.4chan.org', true, true); 467 setcookie('pass_id', null, 1, '/', '.4chan.org', true, true); 468 setcookie('pass_enabled', null, 1, '/', '.4chan.org'); 469 } 470 471 function valid_captcha_bypass() 472 { 473 global $captcha_bypass, $passid, $rangeban_bypass; 474 475 $captcha_bypass = false; 476 $rangeban_bypass = false; 477 478 $passid = ''; 479 480 if (is_local_auth() || has_level('janitor')) { 481 $captcha_bypass = true; 482 $rangeban_bypass = true; 483 return true; 484 } 485 486 if (CAPTCHA != 1) { 487 $captcha_bypass = true; 488 } 489 490 $time = $_SERVER['REQUEST_TIME']; 491 $host = $_SERVER['REMOTE_ADDR']; 492 493 // check for 4chan pass 494 $pass_cookie = isset( $_COOKIE['pass_id'] ) ? $_COOKIE['pass_id'] : ''; 495 496 if (strlen($pass_cookie) == 10) { 497 setcookie('pass_id', '0', 1, '/', '.4chan.org', true, true); 498 setcookie('pass_enabled', '0', 1, '/', '.4chan.org'); 499 error(S_PASSFORMATCHANGED); 500 } 501 502 if ($pass_cookie) { 503 $pass_parts = explode('.', $pass_cookie); 504 505 $pass_user = $pass_parts[0]; 506 $pass_session = $pass_parts[1]; 507 508 if (!$pass_user || !$pass_session) { 509 error(S_INVALIDPASS); 510 } 511 512 // The column is case insensitive but all passes should be uppercase to avoid ban bypassing exploits. 513 $pass_user = strtoupper($pass_user); 514 515 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 516 517 if (!$admin_salt) { 518 die('Internal Server Error (s0)'); 519 } 520 521 $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); 522 523 if( !$passq ) error( S_INVALIDPASS ); 524 525 $res = mysql_fetch_assoc($passq); 526 527 if (!$res || !$res['session_id']) { 528 clear_pass_cookies(); 529 error(S_INVALIDPASS); 530 } 531 532 $hashed_pass_session = substr(hash('sha256', $res['session_id'] . $admin_salt), 0, 32); 533 534 if ($hashed_pass_session !== $pass_session) { 535 clear_pass_cookies(); 536 error(S_INVALIDPASS); 537 } 538 539 if ((int)$res['expiration_date'] <= $time) { 540 clear_pass_cookies(); 541 error(sprintf(S_PASSEXPIRED, $res['pending_id'])); 542 } 543 544 if ($res['status'] != 0) { 545 clear_pass_cookies(); 546 error(S_PASSDISABLED); 547 } 548 549 $lastused = strtotime( $res['last_used'] ); 550 $lastip_mask = ip2long( $res['last_ip'] ) & ( ~255 ); 551 $ip_mask = ip2long( $host ) & ( ~255 ); 552 553 if( $lastip_mask !== 0 && ( $time - $lastused ) < PASS_TIMEOUT && $lastip_mask != $ip_mask ) { 554 555 // old strict code, above is to match last octet 556 //if( ( $time - $lastused ) < PASS_TIMEOUT && $res['last_ip'] != $host && $res['last_ip'] != '0.0.0.0' ) { 557 clear_pass_cookies(); 558 error( S_PASSINUSE ); 559 } 560 561 $update_country = ''; 562 563 if ($res['last_ip'] !== $host) { 564 $geo_data = GeoIP2::get_country($host); 565 566 if ($geo_data && isset($geo_data['country_code'])) { 567 $country_code = $geo_data['country_code']; 568 } 569 else { 570 $country_code = 'XX'; 571 } 572 573 $update_country = ", last_country = '" . mysql_real_escape_string($country_code) . "'"; 574 } 575 576 $passid = $pass_user; 577 578 $captcha_bypass = true; 579 $rangeban_bypass = true; 580 581 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 ); 582 } 583 584 return $captcha_bypass; 585 } 586 587 // some code paths might think current admin name is 4chan_auser cookie 588 // when that's not set (e.g. local requests), assert out here 589 function validate_admin_cookies() 590 { 591 if (!$_COOKIE['4chan_auser']) { 592 error('Internal error (internal request missing name)'); 593 } 594 }