/ lib / auth-test.php
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  }