[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * @package phpBB3 5 * @version $Id$ 6 * @copyright (c) 2005 phpBB Group 7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License 8 * 9 */ 10 11 /** 12 * @ignore 13 */ 14 if (!defined('IN_PHPBB')) 15 { 16 exit; 17 } 18 19 /** 20 * Session class 21 * @package phpBB3 22 */ 23 class session 24 { 25 var $cookie_data = array(); 26 var $page = array(); 27 var $data = array(); 28 var $browser = ''; 29 var $forwarded_for = ''; 30 var $host = ''; 31 var $session_id = ''; 32 var $ip = ''; 33 var $load = 0; 34 var $time_now = 0; 35 var $update_session_page = true; 36 37 /** 38 * Extract current session page 39 * 40 * @param string $root_path current root path (phpbb_root_path) 41 */ 42 function extract_current_page($root_path) 43 { 44 $page_array = array(); 45 46 // First of all, get the request uri... 47 $script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF'); 48 $args = (!empty($_SERVER['QUERY_STRING'])) ? explode('&', $_SERVER['QUERY_STRING']) : explode('&', getenv('QUERY_STRING')); 49 50 // If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support... 51 if (!$script_name) 52 { 53 $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI'); 54 $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name; 55 $page_array['failover'] = 1; 56 } 57 58 // Replace backslashes and doubled slashes (could happen on some proxy setups) 59 $script_name = str_replace(array('\\', '//'), '/', $script_name); 60 61 // Now, remove the sid and let us get a clean query string... 62 $use_args = array(); 63 64 // Since some browser do not encode correctly we need to do this with some "special" characters... 65 // " -> %22, ' => %27, < -> %3C, > -> %3E 66 $find = array('"', "'", '<', '>'); 67 $replace = array('%22', '%27', '%3C', '%3E'); 68 69 foreach ($args as $key => $argument) 70 { 71 if (strpos($argument, 'sid=') === 0) 72 { 73 continue; 74 } 75 76 $use_args[] = str_replace($find, $replace, $argument); 77 } 78 unset($args); 79 80 // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2 81 82 // The current query string 83 $query_string = trim(implode('&', $use_args)); 84 85 // basenamed page name (for example: index.php) 86 $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name); 87 $page_name = urlencode(htmlspecialchars($page_name)); 88 89 // current directory within the phpBB root (for example: adm) 90 $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($root_path))); 91 $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath('./'))); 92 $intersection = array_intersect_assoc($root_dirs, $page_dirs); 93 94 $root_dirs = array_diff_assoc($root_dirs, $intersection); 95 $page_dirs = array_diff_assoc($page_dirs, $intersection); 96 97 $page_dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs); 98 99 if ($page_dir && substr($page_dir, -1, 1) == '/') 100 { 101 $page_dir = substr($page_dir, 0, -1); 102 } 103 104 // Current page from phpBB root (for example: adm/index.php?i=10&b=2) 105 $page = (($page_dir) ? $page_dir . '/' : '') . $page_name . (($query_string) ? "?$query_string" : ''); 106 107 // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in / 108 $script_path = trim(str_replace('\\', '/', dirname($script_name))); 109 110 // The script path from the webroot to the phpBB root (for example: /phpBB3/) 111 $script_dirs = explode('/', $script_path); 112 array_splice($script_dirs, -sizeof($page_dirs)); 113 $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : ''); 114 115 // We are on the base level (phpBB root == webroot), lets adjust the variables a bit... 116 if (!$root_script_path) 117 { 118 $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path; 119 } 120 121 $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/'; 122 $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/'; 123 124 $page_array += array( 125 'page_name' => $page_name, 126 'page_dir' => $page_dir, 127 128 'query_string' => $query_string, 129 'script_path' => str_replace(' ', '%20', htmlspecialchars($script_path)), 130 'root_script_path' => str_replace(' ', '%20', htmlspecialchars($root_script_path)), 131 132 'page' => $page, 133 'forum' => (isset($_REQUEST['f']) && $_REQUEST['f'] > 0) ? (int) $_REQUEST['f'] : 0, 134 ); 135 136 return $page_array; 137 } 138 139 /** 140 * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present. 141 */ 142 function extract_current_hostname() 143 { 144 global $config; 145 146 // Get hostname 147 $host = (!empty($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ((!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME')); 148 149 // Should be a string and lowered 150 $host = (string) strtolower($host); 151 152 // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid 153 if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name'])) 154 { 155 return $host; 156 } 157 158 // Is the host actually a IP? If so, we use the IP... (IPv4) 159 if (long2ip(ip2long($host)) === $host) 160 { 161 return $host; 162 } 163 164 // Now return the hostname (this also removes any port definition). The http:// is prepended to construct a valid URL, hosts never have a scheme assigned 165 $host = @parse_url('http://' . $host); 166 $host = (!empty($host['host'])) ? $host['host'] : ''; 167 168 // Remove any portions not removed by parse_url (#) 169 $host = str_replace('#', '', $host); 170 171 // If, by any means, the host is now empty, we will use a "best approach" way to guess one 172 if (empty($host)) 173 { 174 if (!empty($config['server_name'])) 175 { 176 $host = $config['server_name']; 177 } 178 else if (!empty($config['cookie_domain'])) 179 { 180 $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain']; 181 } 182 else 183 { 184 // Set to OS hostname or localhost 185 $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost'; 186 } 187 } 188 189 // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set) 190 return $host; 191 } 192 193 /** 194 * Start session management 195 * 196 * This is where all session activity begins. We gather various pieces of 197 * information from the client and server. We test to see if a session already 198 * exists. If it does, fine and dandy. If it doesn't we'll go on to create a 199 * new one ... pretty logical heh? We also examine the system load (if we're 200 * running on a system which makes such information readily available) and 201 * halt if it's above an admin definable limit. 202 * 203 * @param bool $update_session_page if true the session page gets updated. 204 * This can be set to circumvent certain scripts to update the users last visited page. 205 */ 206 function session_begin($update_session_page = true) 207 { 208 global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path; 209 210 // Give us some basic information 211 $this->time_now = time(); 212 $this->cookie_data = array('u' => 0, 'k' => ''); 213 $this->update_session_page = $update_session_page; 214 $this->browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : ''; 215 $this->referer = (!empty($_SERVER['HTTP_REFERER'])) ? htmlspecialchars((string) $_SERVER['HTTP_REFERER']) : ''; 216 $this->forwarded_for = (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? htmlspecialchars((string) $_SERVER['HTTP_X_FORWARDED_FOR']) : ''; 217 218 $this->host = $this->extract_current_hostname(); 219 $this->page = $this->extract_current_page($phpbb_root_path); 220 221 // if the forwarded for header shall be checked we have to validate its contents 222 if ($config['forwarded_for_check']) 223 { 224 $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for)); 225 226 // split the list of IPs 227 $ips = explode(' ', $this->forwarded_for); 228 foreach ($ips as $ip) 229 { 230 // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly 231 if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) 232 { 233 // contains invalid data, don't use the forwarded for header 234 $this->forwarded_for = ''; 235 break; 236 } 237 } 238 } 239 else 240 { 241 $this->forwarded_for = ''; 242 } 243 244 if (isset($_COOKIE[$config['cookie_name'] . '_sid']) || isset($_COOKIE[$config['cookie_name'] . '_u'])) 245 { 246 $this->cookie_data['u'] = request_var($config['cookie_name'] . '_u', 0, false, true); 247 $this->cookie_data['k'] = request_var($config['cookie_name'] . '_k', '', false, true); 248 $this->session_id = request_var($config['cookie_name'] . '_sid', '', false, true); 249 250 $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid='; 251 $_SID = (defined('NEED_SID')) ? $this->session_id : ''; 252 253 if (empty($this->session_id)) 254 { 255 $this->session_id = $_SID = request_var('sid', ''); 256 $SID = '?sid=' . $this->session_id; 257 $this->cookie_data = array('u' => 0, 'k' => ''); 258 } 259 } 260 else 261 { 262 $this->session_id = $_SID = request_var('sid', ''); 263 $SID = '?sid=' . $this->session_id; 264 } 265 266 $_EXTRA_URL = array(); 267 268 // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests 269 // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip. 270 $this->ip = (!empty($_SERVER['REMOTE_ADDR'])) ? (string) $_SERVER['REMOTE_ADDR'] : ''; 271 $this->ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->ip)); 272 273 // split the list of IPs 274 $ips = explode(' ', trim($this->ip)); 275 276 // Default IP if REMOTE_ADDR is invalid 277 $this->ip = '127.0.0.1'; 278 279 foreach ($ips as $ip) 280 { 281 if (preg_match(get_preg_expression('ipv4'), $ip)) 282 { 283 $this->ip = $ip; 284 } 285 else if (preg_match(get_preg_expression('ipv6'), $ip)) 286 { 287 // Quick check for IPv4-mapped address in IPv6 288 if (stripos($ip, '::ffff:') === 0) 289 { 290 $ipv4 = substr($ip, 7); 291 292 if (preg_match(get_preg_expression('ipv4'), $ipv4)) 293 { 294 $ip = $ipv4; 295 } 296 } 297 298 $this->ip = $ip; 299 } 300 else 301 { 302 // We want to use the last valid address in the chain 303 // Leave foreach loop when address is invalid 304 break; 305 } 306 } 307 308 $this->load = false; 309 310 // Load limit check (if applicable) 311 if ($config['limit_load'] || $config['limit_search_load']) 312 { 313 if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg')))) 314 { 315 $this->load = array_slice($load, 0, 1); 316 $this->load = floatval($this->load[0]); 317 } 318 else 319 { 320 set_config('limit_load', '0'); 321 set_config('limit_search_load', '0'); 322 } 323 } 324 325 // if no session id is set, redirect to index.php 326 if (defined('NEED_SID') && (!isset($_GET['sid']) || $this->session_id !== $_GET['sid'])) 327 { 328 send_status_line(401, 'Unauthorized'); 329 redirect(append_sid("{$phpbb_root_path}index.$phpEx")); 330 } 331 332 // if session id is set 333 if (!empty($this->session_id)) 334 { 335 $sql = 'SELECT u.*, s.* 336 FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u 337 WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "' 338 AND u.user_id = s.session_user_id"; 339 $result = $db->sql_query($sql); 340 $this->data = $db->sql_fetchrow($result); 341 $db->sql_freeresult($result); 342 343 // Did the session exist in the DB? 344 if (isset($this->data['user_id'])) 345 { 346 // Validate IP length according to admin ... enforces an IP 347 // check on bots if admin requires this 348 // $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check']; 349 350 if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) 351 { 352 $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); 353 $u_ip = short_ipv6($this->ip, $config['ip_check']); 354 } 355 else 356 { 357 $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); 358 $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); 359 } 360 361 $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; 362 $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; 363 364 $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; 365 $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; 366 367 // referer checks 368 // The @ before $config['referer_validation'] suppresses notices present while running the updater 369 $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH); 370 $referer_valid = true; 371 372 // we assume HEAD and TRACE to be foul play and thus only whitelist GET 373 if (@$config['referer_validation'] && isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) !== 'get') 374 { 375 $referer_valid = $this->validate_referer($check_referer_path); 376 } 377 378 if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid) 379 { 380 $session_expired = false; 381 382 // Check whether the session is still valid if we have one 383 $method = basename(trim($config['auth_method'])); 384 include_once($phpbb_root_path . 'includes/auth/auth_' . $method . '.' . $phpEx); 385 386 $method = 'validate_session_' . $method; 387 if (function_exists($method)) 388 { 389 if (!$method($this->data)) 390 { 391 $session_expired = true; 392 } 393 } 394 395 if (!$session_expired) 396 { 397 // Check the session length timeframe if autologin is not enabled. 398 // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide. 399 if (!$this->data['session_autologin']) 400 { 401 if ($this->data['session_time'] < $this->time_now - ($config['session_length'] + 60)) 402 { 403 $session_expired = true; 404 } 405 } 406 else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60)) 407 { 408 $session_expired = true; 409 } 410 } 411 412 if (!$session_expired) 413 { 414 // Only update session DB a minute or so after last update or if page changes 415 if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) 416 { 417 $sql_ary = array('session_time' => $this->time_now); 418 419 if ($this->update_session_page) 420 { 421 $sql_ary['session_page'] = substr($this->page['page'], 0, 199); 422 $sql_ary['session_forum_id'] = $this->page['forum']; 423 } 424 425 $db->sql_return_on_error(true); 426 427 $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " 428 WHERE session_id = '" . $db->sql_escape($this->session_id) . "'"; 429 $result = $db->sql_query($sql); 430 431 $db->sql_return_on_error(false); 432 433 // If the database is not yet updated, there will be an error due to the session_forum_id 434 // @todo REMOVE for 3.0.2 435 if ($result === false) 436 { 437 unset($sql_ary['session_forum_id']); 438 439 $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " 440 WHERE session_id = '" . $db->sql_escape($this->session_id) . "'"; 441 $db->sql_query($sql); 442 } 443 444 if ($this->data['user_id'] != ANONYMOUS && !empty($config['new_member_post_limit']) && $this->data['user_new'] && $config['new_member_post_limit'] <= $this->data['user_posts']) 445 { 446 $this->leave_newly_registered(); 447 } 448 } 449 450 $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; 451 $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false; 452 $this->data['user_lang'] = basename($this->data['user_lang']); 453 454 return true; 455 } 456 } 457 else 458 { 459 // Added logging temporarly to help debug bugs... 460 if (defined('DEBUG_EXTRA') && $this->data['user_id'] != ANONYMOUS) 461 { 462 if ($referer_valid) 463 { 464 add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); 465 } 466 else 467 { 468 add_log('critical', 'LOG_REFERER_INVALID', $this->referer); 469 } 470 } 471 } 472 } 473 } 474 475 // If we reach here then no (valid) session exists. So we'll create a new one 476 return $this->session_create(); 477 } 478 479 /** 480 * Create a new session 481 * 482 * If upon trying to start a session we discover there is nothing existing we 483 * jump here. Additionally this method is called directly during login to regenerate 484 * the session for the specific user. In this method we carry out a number of tasks; 485 * garbage collection, (search)bot checking, banned user comparison. Basically 486 * though this method will result in a new session for a specific user. 487 */ 488 function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true) 489 { 490 global $SID, $_SID, $db, $config, $cache, $phpbb_root_path, $phpEx; 491 492 $this->data = array(); 493 494 /* Garbage collection ... remove old sessions updating user information 495 // if necessary. It means (potentially) 11 queries but only infrequently 496 if ($this->time_now > $config['session_last_gc'] + $config['session_gc']) 497 { 498 $this->session_gc(); 499 }*/ 500 501 // Do we allow autologin on this board? No? Then override anything 502 // that may be requested here 503 if (!$config['allow_autologin']) 504 { 505 $this->cookie_data['k'] = $persist_login = false; 506 } 507 508 /** 509 * Here we do a bot check, oh er saucy! No, not that kind of bot 510 * check. We loop through the list of bots defined by the admin and 511 * see if we have any useragent and/or IP matches. If we do, this is a 512 * bot, act accordingly 513 */ 514 $bot = false; 515 $active_bots = $cache->obtain_bots(); 516 517 foreach ($active_bots as $row) 518 { 519 if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser)) 520 { 521 $bot = $row['user_id']; 522 } 523 524 // If ip is supplied, we will make sure the ip is matching too... 525 if ($row['bot_ip'] && ($bot || !$row['bot_agent'])) 526 { 527 // Set bot to false, then we only have to set it to true if it is matching 528 $bot = false; 529 530 foreach (explode(',', $row['bot_ip']) as $bot_ip) 531 { 532 $bot_ip = trim($bot_ip); 533 534 if (!$bot_ip) 535 { 536 continue; 537 } 538 539 if (strpos($this->ip, $bot_ip) === 0) 540 { 541 $bot = (int) $row['user_id']; 542 break; 543 } 544 } 545 } 546 547 if ($bot) 548 { 549 break; 550 } 551 } 552 553 $method = basename(trim($config['auth_method'])); 554 include_once($phpbb_root_path . 'includes/auth/auth_' . $method . '.' . $phpEx); 555 556 $method = 'autologin_' . $method; 557 if (function_exists($method)) 558 { 559 $this->data = $method(); 560 561 if (sizeof($this->data)) 562 { 563 $this->cookie_data['k'] = ''; 564 $this->cookie_data['u'] = $this->data['user_id']; 565 } 566 } 567 568 // If we're presented with an autologin key we'll join against it. 569 // Else if we've been passed a user_id we'll grab data based on that 570 if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && !sizeof($this->data)) 571 { 572 $sql = 'SELECT u.* 573 FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k 574 WHERE u.user_id = ' . (int) $this->cookie_data['u'] . ' 575 AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ") 576 AND k.user_id = u.user_id 577 AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; 578 $result = $db->sql_query($sql); 579 $this->data = $db->sql_fetchrow($result); 580 $db->sql_freeresult($result); 581 $bot = false; 582 } 583 else if ($user_id !== false && !sizeof($this->data)) 584 { 585 $this->cookie_data['k'] = ''; 586 $this->cookie_data['u'] = $user_id; 587 588 $sql = 'SELECT * 589 FROM ' . USERS_TABLE . ' 590 WHERE user_id = ' . (int) $this->cookie_data['u'] . ' 591 AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; 592 $result = $db->sql_query($sql); 593 $this->data = $db->sql_fetchrow($result); 594 $db->sql_freeresult($result); 595 $bot = false; 596 } 597 598 // Bot user, if they have a SID in the Request URI we need to get rid of it 599 // otherwise they'll index this page with the SID, duplicate content oh my! 600 if ($bot && isset($_GET['sid'])) 601 { 602 send_status_line(301, 'Moved Permanently'); 603 redirect(build_url(array('sid'))); 604 } 605 606 // If no data was returned one or more of the following occurred: 607 // Key didn't match one in the DB 608 // User does not exist 609 // User is inactive 610 // User is bot 611 if (!sizeof($this->data) || !is_array($this->data)) 612 { 613 $this->cookie_data['k'] = ''; 614 $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS; 615 616 if (!$bot) 617 { 618 $sql = 'SELECT * 619 FROM ' . USERS_TABLE . ' 620 WHERE user_id = ' . (int) $this->cookie_data['u']; 621 } 622 else 623 { 624 // We give bots always the same session if it is not yet expired. 625 $sql = 'SELECT u.*, s.* 626 FROM ' . USERS_TABLE . ' u 627 LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) 628 WHERE u.user_id = ' . (int) $bot; 629 } 630 631 $result = $db->sql_query($sql); 632 $this->data = $db->sql_fetchrow($result); 633 $db->sql_freeresult($result); 634 } 635 636 if ($this->data['user_id'] != ANONYMOUS && !$bot) 637 { 638 $this->data['session_last_visit'] = (isset($this->data['session_time']) && $this->data['session_time']) ? $this->data['session_time'] : (($this->data['user_lastvisit']) ? $this->data['user_lastvisit'] : time()); 639 } 640 else 641 { 642 $this->data['session_last_visit'] = $this->time_now; 643 } 644 645 // Force user id to be integer... 646 $this->data['user_id'] = (int) $this->data['user_id']; 647 648 // At this stage we should have a filled data array, defined cookie u and k data. 649 // data array should contain recent session info if we're a real user and a recent 650 // session exists in which case session_id will also be set 651 652 // Is user banned? Are they excluded? Won't return on ban, exists within method 653 if ($this->data['user_type'] != USER_FOUNDER) 654 { 655 if (!$config['forwarded_for_check']) 656 { 657 $this->check_ban($this->data['user_id'], $this->ip); 658 } 659 else 660 { 661 $ips = explode(' ', $this->forwarded_for); 662 $ips[] = $this->ip; 663 $this->check_ban($this->data['user_id'], $ips); 664 } 665 } 666 667 $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; 668 $this->data['is_bot'] = ($bot) ? true : false; 669 670 // If our friend is a bot, we re-assign a previously assigned session 671 if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id']) 672 { 673 // Only assign the current session if the ip, browser and forwarded_for match... 674 if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) 675 { 676 $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); 677 $u_ip = short_ipv6($this->ip, $config['ip_check']); 678 } 679 else 680 { 681 $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); 682 $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); 683 } 684 685 $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; 686 $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; 687 688 $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; 689 $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; 690 691 if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) 692 { 693 $this->session_id = $this->data['session_id']; 694 695 // Only update session DB a minute or so after last update or if page changes 696 if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) 697 { 698 $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; 699 700 $sql_ary = array('session_time' => $this->time_now, 'session_last_visit' => $this->time_now, 'session_admin' => 0); 701 702 if ($this->update_session_page) 703 { 704 $sql_ary['session_page'] = substr($this->page['page'], 0, 199); 705 $sql_ary['session_forum_id'] = $this->page['forum']; 706 } 707 708 $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " 709 WHERE session_id = '" . $db->sql_escape($this->session_id) . "'"; 710 $db->sql_query($sql); 711 712 // Update the last visit time 713 $sql = 'UPDATE ' . USERS_TABLE . ' 714 SET user_lastvisit = ' . (int) $this->data['session_time'] . ' 715 WHERE user_id = ' . (int) $this->data['user_id']; 716 $db->sql_query($sql); 717 } 718 719 $SID = '?sid='; 720 $_SID = ''; 721 return true; 722 } 723 else 724 { 725 // If the ip and browser does not match make sure we only have one bot assigned to one session 726 $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']); 727 } 728 } 729 730 $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false; 731 $set_admin = ($set_admin && $this->data['is_registered']) ? true : false; 732 733 // Create or update the session 734 $sql_ary = array( 735 'session_user_id' => (int) $this->data['user_id'], 736 'session_start' => (int) $this->time_now, 737 'session_last_visit' => (int) $this->data['session_last_visit'], 738 'session_time' => (int) $this->time_now, 739 'session_browser' => (string) trim(substr($this->browser, 0, 149)), 740 'session_forwarded_for' => (string) $this->forwarded_for, 741 'session_ip' => (string) $this->ip, 742 'session_autologin' => ($session_autologin) ? 1 : 0, 743 'session_admin' => ($set_admin) ? 1 : 0, 744 'session_viewonline' => ($viewonline) ? 1 : 0, 745 ); 746 747 if ($this->update_session_page) 748 { 749 $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); 750 $sql_ary['session_forum_id'] = $this->page['forum']; 751 } 752 753 $db->sql_return_on_error(true); 754 755 $sql = 'DELETE 756 FROM ' . SESSIONS_TABLE . ' 757 WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\' 758 AND session_user_id = ' . ANONYMOUS; 759 760 if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows())) 761 { 762 // Limit new sessions in 1 minute period (if required) 763 if (empty($this->data['session_time']) && $config['active_sessions']) 764 { 765 // $db->sql_return_on_error(false); 766 767 $sql = 'SELECT COUNT(session_id) AS sessions 768 FROM ' . SESSIONS_TABLE . ' 769 WHERE session_time >= ' . ($this->time_now - 60); 770 $result = $db->sql_query($sql); 771 $row = $db->sql_fetchrow($result); 772 $db->sql_freeresult($result); 773 774 if ((int) $row['sessions'] > (int) $config['active_sessions']) 775 { 776 send_status_line(503, 'Service Unavailable'); 777 trigger_error('BOARD_UNAVAILABLE'); 778 } 779 } 780 } 781 782 // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors. 783 // Commented out because it will not allow forums to update correctly 784 // $db->sql_return_on_error(false); 785 786 // Something quite important: session_page always holds the *last* page visited, except for the *first* visit. 787 // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case. 788 // If the session id is empty, we have a completely new one and will set an "identifier" here. This identifier is able to be checked later. 789 if (empty($this->data['session_id'])) 790 { 791 // This is a temporary variable, only set for the very first visit 792 $this->data['session_created'] = true; 793 } 794 795 $this->session_id = $this->data['session_id'] = md5(unique_id()); 796 797 $sql_ary['session_id'] = (string) $this->session_id; 798 $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); 799 $sql_ary['session_forum_id'] = $this->page['forum']; 800 801 $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); 802 $db->sql_query($sql); 803 804 $db->sql_return_on_error(false); 805 806 // Regenerate autologin/persistent login key 807 if ($session_autologin) 808 { 809 $this->set_login_key(); 810 } 811 812 // refresh data 813 $SID = '?sid=' . $this->session_id; 814 $_SID = $this->session_id; 815 $this->data = array_merge($this->data, $sql_ary); 816 817 if (!$bot) 818 { 819 $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000); 820 821 $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire); 822 $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire); 823 $this->set_cookie('sid', $this->session_id, $cookie_expire); 824 825 unset($cookie_expire); 826 827 $sql = 'SELECT COUNT(session_id) AS sessions 828 FROM ' . SESSIONS_TABLE . ' 829 WHERE session_user_id = ' . (int) $this->data['user_id'] . ' 830 AND session_time >= ' . (int) ($this->time_now - (max($config['session_length'], $config['form_token_lifetime']))); 831 $result = $db->sql_query($sql); 832 $row = $db->sql_fetchrow($result); 833 $db->sql_freeresult($result); 834 835 if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt'])) 836 { 837 $this->data['user_form_salt'] = unique_id(); 838 // Update the form key 839 $sql = 'UPDATE ' . USERS_TABLE . ' 840 SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\' 841 WHERE user_id = ' . (int) $this->data['user_id']; 842 $db->sql_query($sql); 843 } 844 } 845 else 846 { 847 $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; 848 849 // Update the last visit time 850 $sql = 'UPDATE ' . USERS_TABLE . ' 851 SET user_lastvisit = ' . (int) $this->data['session_time'] . ' 852 WHERE user_id = ' . (int) $this->data['user_id']; 853 $db->sql_query($sql); 854 855 $SID = '?sid='; 856 $_SID = ''; 857 } 858 859 return true; 860 } 861 862 /** 863 * Kills a session 864 * 865 * This method does what it says on the tin. It will delete a pre-existing session. 866 * It resets cookie information (destroying any autologin key within that cookie data) 867 * and update the users information from the relevant session data. It will then 868 * grab guest user information. 869 */ 870 function session_kill($new_session = true) 871 { 872 global $SID, $_SID, $db, $config, $phpbb_root_path, $phpEx; 873 874 $sql = 'DELETE FROM ' . SESSIONS_TABLE . " 875 WHERE session_id = '" . $db->sql_escape($this->session_id) . "' 876 AND session_user_id = " . (int) $this->data['user_id']; 877 $db->sql_query($sql); 878 879 // Allow connecting logout with external auth method logout 880 $method = basename(trim($config['auth_method'])); 881 include_once($phpbb_root_path . 'includes/auth/auth_' . $method . '.' . $phpEx); 882 883 $method = 'logout_' . $method; 884 if (function_exists($method)) 885 { 886 $method($this->data, $new_session); 887 } 888 889 if ($this->data['user_id'] != ANONYMOUS) 890 { 891 // Delete existing session, update last visit info first! 892 if (!isset($this->data['session_time'])) 893 { 894 $this->data['session_time'] = time(); 895 } 896 897 $sql = 'UPDATE ' . USERS_TABLE . ' 898 SET user_lastvisit = ' . (int) $this->data['session_time'] . ' 899 WHERE user_id = ' . (int) $this->data['user_id']; 900 $db->sql_query($sql); 901 902 if ($this->cookie_data['k']) 903 { 904 $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' 905 WHERE user_id = ' . (int) $this->data['user_id'] . " 906 AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; 907 $db->sql_query($sql); 908 } 909 910 // Reset the data array 911 $this->data = array(); 912 913 $sql = 'SELECT * 914 FROM ' . USERS_TABLE . ' 915 WHERE user_id = ' . ANONYMOUS; 916 $result = $db->sql_query($sql); 917 $this->data = $db->sql_fetchrow($result); 918 $db->sql_freeresult($result); 919 } 920 921 $cookie_expire = $this->time_now - 31536000; 922 $this->set_cookie('u', '', $cookie_expire); 923 $this->set_cookie('k', '', $cookie_expire); 924 $this->set_cookie('sid', '', $cookie_expire); 925 unset($cookie_expire); 926 927 $SID = '?sid='; 928 $this->session_id = $_SID = ''; 929 930 // To make sure a valid session is created we create one for the anonymous user 931 if ($new_session) 932 { 933 $this->session_create(ANONYMOUS); 934 } 935 936 return true; 937 } 938 939 /** 940 * Session garbage collection 941 * 942 * This looks a lot more complex than it really is. Effectively we are 943 * deleting any sessions older than an admin definable limit. Due to the 944 * way in which we maintain session data we have to ensure we update user 945 * data before those sessions are destroyed. In addition this method 946 * removes autologin key information that is older than an admin defined 947 * limit. 948 */ 949 function session_gc() 950 { 951 global $db, $config, $phpbb_root_path, $phpEx; 952 953 $batch_size = 10; 954 955 if (!$this->time_now) 956 { 957 $this->time_now = time(); 958 } 959 960 // Firstly, delete guest sessions 961 $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' 962 WHERE session_user_id = ' . ANONYMOUS . ' 963 AND session_time < ' . (int) ($this->time_now - $config['session_length']); 964 $db->sql_query($sql); 965 966 // Get expired sessions, only most recent for each user 967 $sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time 968 FROM ' . SESSIONS_TABLE . ' 969 WHERE session_time < ' . ($this->time_now - $config['session_length']) . ' 970 GROUP BY session_user_id, session_page'; 971 $result = $db->sql_query_limit($sql, $batch_size); 972 973 $del_user_id = array(); 974 $del_sessions = 0; 975 976 while ($row = $db->sql_fetchrow($result)) 977 { 978 $sql = 'UPDATE ' . USERS_TABLE . ' 979 SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' 980 WHERE user_id = " . (int) $row['session_user_id']; 981 $db->sql_query($sql); 982 983 $del_user_id[] = (int) $row['session_user_id']; 984 $del_sessions++; 985 } 986 $db->sql_freeresult($result); 987 988 if (sizeof($del_user_id)) 989 { 990 // Delete expired sessions 991 $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' 992 WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . ' 993 AND session_time < ' . ($this->time_now - $config['session_length']); 994 $db->sql_query($sql); 995 } 996 997 if ($del_sessions < $batch_size) 998 { 999 // Less than 10 users, update gc timer ... else we want gc 1000 // called again to delete other sessions 1001 set_config('session_last_gc', $this->time_now, true); 1002 1003 if ($config['max_autologin_time']) 1004 { 1005 $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' 1006 WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time'])); 1007 $db->sql_query($sql); 1008 } 1009 1010 // only called from CRON; should be a safe workaround until the infrastructure gets going 1011 if (!class_exists('phpbb_captcha_factory')) 1012 { 1013 include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx); 1014 } 1015 phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); 1016 1017 $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' 1018 WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); 1019 $db->sql_query($sql); 1020 } 1021 1022 return; 1023 } 1024 1025 /** 1026 * Sets a cookie 1027 * 1028 * Sets a cookie of the given name with the specified data for the given length of time. If no time is specified, a session cookie will be set. 1029 * 1030 * @param string $name Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then. 1031 * @param string $cookiedata The data to hold within the cookie 1032 * @param int $cookietime The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set. 1033 */ 1034 function set_cookie($name, $cookiedata, $cookietime) 1035 { 1036 global $config; 1037 1038 $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata); 1039 $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime); 1040 $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']; 1041 1042 header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false); 1043 } 1044 1045 /** 1046 * Check for banned user 1047 * 1048 * Checks whether the supplied user is banned by id, ip or email. If no parameters 1049 * are passed to the method pre-existing session data is used. If $return is false 1050 * this routine does not return on finding a banned user, it outputs a relevant 1051 * message and stops execution. 1052 * 1053 * @param string|array $user_ips Can contain a string with one IP or an array of multiple IPs 1054 */ 1055 function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) 1056 { 1057 global $config, $db; 1058 1059 if (defined('IN_CHECK_BAN')) 1060 { 1061 return; 1062 } 1063 1064 $banned = false; 1065 $cache_ttl = 3600; 1066 $where_sql = array(); 1067 1068 $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end 1069 FROM ' . BANLIST_TABLE . ' 1070 WHERE '; 1071 1072 // Determine which entries to check, only return those 1073 if ($user_email === false) 1074 { 1075 $where_sql[] = "ban_email = ''"; 1076 } 1077 1078 if ($user_ips === false) 1079 { 1080 $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)"; 1081 } 1082 1083 if ($user_id === false) 1084 { 1085 $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)'; 1086 } 1087 else 1088 { 1089 $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0; 1090 $_sql = '(ban_userid = ' . $user_id; 1091 1092 if ($user_email !== false) 1093 { 1094 $_sql .= " OR ban_email <> ''"; 1095 } 1096 1097 if ($user_ips !== false) 1098 { 1099 $_sql .= " OR ban_ip <> ''"; 1100 } 1101 1102 $_sql .= ')'; 1103 1104 $where_sql[] = $_sql; 1105 } 1106 1107 $sql .= (sizeof($where_sql)) ? implode(' AND ', $where_sql) : ''; 1108 $result = $db->sql_query($sql, $cache_ttl); 1109 1110 $ban_triggered_by = 'user'; 1111 while ($row = $db->sql_fetchrow($result)) 1112 { 1113 if ($row['ban_end'] && $row['ban_end'] < time()) 1114 { 1115 continue; 1116 } 1117 1118 $ip_banned = false; 1119 if (!empty($row['ban_ip'])) 1120 { 1121 if (!is_array($user_ips)) 1122 { 1123 $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips); 1124 } 1125 else 1126 { 1127 foreach ($user_ips as $user_ip) 1128 { 1129 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip)) 1130 { 1131 $ip_banned = true; 1132 break; 1133 } 1134 } 1135 } 1136 } 1137 1138 if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || 1139 $ip_banned || 1140 (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email))) 1141 { 1142 if (!empty($row['ban_exclude'])) 1143 { 1144 $banned = false; 1145 break; 1146 } 1147 else 1148 { 1149 $banned = true; 1150 $ban_row = $row; 1151 1152 if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) 1153 { 1154 $ban_triggered_by = 'user'; 1155 } 1156 else if ($ip_banned) 1157 { 1158 $ban_triggered_by = 'ip'; 1159 } 1160 else 1161 { 1162 $ban_triggered_by = 'email'; 1163 } 1164 1165 // Don't break. Check if there is an exclude rule for this user 1166 } 1167 } 1168 } 1169 $db->sql_freeresult($result); 1170 1171 if ($banned && !$return) 1172 { 1173 global $template; 1174 1175 // If the session is empty we need to create a valid one... 1176 if (empty($this->session_id)) 1177 { 1178 // This seems to be no longer needed? - #14971 1179 // $this->session_create(ANONYMOUS); 1180 } 1181 1182 // Initiate environment ... since it won't be set at this stage 1183 $this->setup(); 1184 1185 // Logout the user, banned users are unable to use the normal 'logout' link 1186 if ($this->data['user_id'] != ANONYMOUS) 1187 { 1188 $this->session_kill(); 1189 } 1190 1191 // We show a login box here to allow founders accessing the board if banned by IP 1192 if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS) 1193 { 1194 global $phpEx; 1195 1196 $this->setup('ucp'); 1197 $this->data['is_registered'] = $this->data['is_bot'] = false; 1198 1199 // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again. 1200 define('IN_CHECK_BAN', 1); 1201 1202 login_box("index.$phpEx"); 1203 1204 // The false here is needed, else the user is able to circumvent the ban. 1205 $this->session_kill(false); 1206 } 1207 1208 // Ok, we catch the case of an empty session id for the anonymous user... 1209 // This can happen if the user is logging in, banned by username and the login_box() being called "again". 1210 if (empty($this->session_id) && defined('IN_CHECK_BAN')) 1211 { 1212 $this->session_create(ANONYMOUS); 1213 } 1214 1215 1216 // Determine which message to output 1217 $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; 1218 $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; 1219 1220 $message = sprintf($this->lang[$message], $till_date, '<a href="mailto:' . $config['board_contact'] . '">', '</a>'); 1221 $message .= ($ban_row['ban_give_reason']) ? '<br /><br />' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : ''; 1222 $message .= '<br /><br /><em>' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . '</em>'; 1223 1224 // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again 1225 $this->session_kill(false); 1226 1227 // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page 1228 if (defined('IN_CRON')) 1229 { 1230 garbage_collection(); 1231 exit_handler(); 1232 exit; 1233 } 1234 1235 trigger_error($message); 1236 } 1237 1238 return ($banned && $ban_row['ban_give_reason']) ? $ban_row['ban_give_reason'] : $banned; 1239 } 1240 1241 /** 1242 * Check if ip is blacklisted 1243 * This should be called only where absolutly necessary 1244 * 1245 * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups) 1246 * 1247 * @author satmd (from the php manual) 1248 * @param string $mode register/post - spamcop for example is ommitted for posting 1249 * @return false if ip is not blacklisted, else an array([checked server], [lookup]) 1250 */ 1251 function check_dnsbl($mode, $ip = false) 1252 { 1253 if ($ip === false) 1254 { 1255 $ip = $this->ip; 1256 } 1257 1258 // Neither Spamhaus nor Spamcop supports IPv6 addresses. 1259 if (strpos($ip, ':') !== false) 1260 { 1261 return false; 1262 } 1263 1264 $dnsbl_check = array( 1265 'sbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=', 1266 ); 1267 1268 if ($mode == 'register') 1269 { 1270 $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?'; 1271 } 1272 1273 if ($ip) 1274 { 1275 $quads = explode('.', $ip); 1276 $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0]; 1277 1278 // Need to be listed on all servers... 1279 $listed = true; 1280 $info = array(); 1281 1282 foreach ($dnsbl_check as $dnsbl => $lookup) 1283 { 1284 if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true) 1285 { 1286 $info = array($dnsbl, $lookup . $ip); 1287 } 1288 else 1289 { 1290 $listed = false; 1291 } 1292 } 1293 1294 if ($listed) 1295 { 1296 return $info; 1297 } 1298 } 1299 1300 return false; 1301 } 1302 1303 /** 1304 * Check if URI is blacklisted 1305 * This should be called only where absolutly necessary, for example on the submitted website field 1306 * This function is not in use at the moment and is only included for testing purposes, it may not work at all! 1307 * This means it is untested at the moment and therefore commented out 1308 * 1309 * @param string $uri URI to check 1310 * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists 1311 function check_uribl($uri) 1312 { 1313 // Normally parse_url() is not intended to parse uris 1314 // We need to get the top-level domain name anyway... change. 1315 $uri = parse_url($uri); 1316 1317 if ($uri === false || empty($uri['host'])) 1318 { 1319 return false; 1320 } 1321 1322 $uri = trim($uri['host']); 1323 1324 if ($uri) 1325 { 1326 // One problem here... the return parameter for the "windows" method is different from what 1327 // we expect... this may render this check useless... 1328 if (phpbb_checkdnsrr($uri . '.multi.uribl.com.', 'A') === true) 1329 { 1330 return true; 1331 } 1332 } 1333 1334 return false; 1335 } 1336 */ 1337 1338 /** 1339 * Set/Update a persistent login key 1340 * 1341 * This method creates or updates a persistent session key. When a user makes 1342 * use of persistent (formerly auto-) logins a key is generated and stored in the 1343 * DB. When they revisit with the same key it's automatically updated in both the 1344 * DB and cookie. Multiple keys may exist for each user representing different 1345 * browsers or locations. As with _any_ non-secure-socket no passphrase login this 1346 * remains vulnerable to exploit. 1347 */ 1348 function set_login_key($user_id = false, $key = false, $user_ip = false) 1349 { 1350 global $config, $db; 1351 1352 $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id; 1353 $user_ip = ($user_ip === false) ? $this->ip : $user_ip; 1354 $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key; 1355 1356 $key_id = unique_id(hexdec(substr($this->session_id, 0, 8))); 1357 1358 $sql_ary = array( 1359 'key_id' => (string) md5($key_id), 1360 'last_ip' => (string) $this->ip, 1361 'last_login' => (int) time() 1362 ); 1363 1364 if (!$key) 1365 { 1366 $sql_ary += array( 1367 'user_id' => (int) $user_id 1368 ); 1369 } 1370 1371 if ($key) 1372 { 1373 $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . ' 1374 SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' 1375 WHERE user_id = ' . (int) $user_id . " 1376 AND key_id = '" . $db->sql_escape(md5($key)) . "'"; 1377 } 1378 else 1379 { 1380 $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); 1381 } 1382 $db->sql_query($sql); 1383 1384 $this->cookie_data['k'] = $key_id; 1385 1386 return false; 1387 } 1388 1389 /** 1390 * Reset all login keys for the specified user 1391 * 1392 * This method removes all current login keys for a specified (or the current) 1393 * user. It will be called on password change to render old keys unusable 1394 */ 1395 function reset_login_keys($user_id = false) 1396 { 1397 global $config, $db; 1398 1399 $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id; 1400 1401 $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' 1402 WHERE user_id = ' . (int) $user_id; 1403 $db->sql_query($sql); 1404 1405 // If the user is logged in, update last visit info first before deleting sessions 1406 $sql = 'SELECT session_time, session_page 1407 FROM ' . SESSIONS_TABLE . ' 1408 WHERE session_user_id = ' . (int) $user_id . ' 1409 ORDER BY session_time DESC'; 1410 $result = $db->sql_query_limit($sql, 1); 1411 $row = $db->sql_fetchrow($result); 1412 $db->sql_freeresult($result); 1413 1414 if ($row) 1415 { 1416 $sql = 'UPDATE ' . USERS_TABLE . ' 1417 SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' 1418 WHERE user_id = " . (int) $user_id; 1419 $db->sql_query($sql); 1420 } 1421 1422 // Let's also clear any current sessions for the specified user_id 1423 // If it's the current user then we'll leave this session intact 1424 $sql_where = 'session_user_id = ' . (int) $user_id; 1425 $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : ''; 1426 1427 $sql = 'DELETE FROM ' . SESSIONS_TABLE . " 1428 WHERE $sql_where"; 1429 $db->sql_query($sql); 1430 1431 // We're changing the password of the current user and they have a key 1432 // Lets regenerate it to be safe 1433 if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k']) 1434 { 1435 $this->set_login_key($user_id); 1436 } 1437 } 1438 1439 1440 /** 1441 * Check if the request originated from the same page. 1442 * @param bool $check_script_path If true, the path will be checked as well 1443 */ 1444 function validate_referer($check_script_path = false) 1445 { 1446 global $config; 1447 1448 // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason) 1449 if (empty($this->referer) || empty($this->host)) 1450 { 1451 return true; 1452 } 1453 1454 $host = htmlspecialchars($this->host); 1455 $ref = substr($this->referer, strpos($this->referer, '://') + 3); 1456 1457 if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0))) 1458 { 1459 return false; 1460 } 1461 else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '') 1462 { 1463 $ref = substr($ref, strlen($host)); 1464 $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT'); 1465 1466 if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0) 1467 { 1468 $ref = substr($ref, strlen(":$server_port")); 1469 } 1470 1471 if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0)) 1472 { 1473 return false; 1474 } 1475 } 1476 1477 return true; 1478 } 1479 1480 1481 function unset_admin() 1482 { 1483 global $db; 1484 $sql = 'UPDATE ' . SESSIONS_TABLE . ' 1485 SET session_admin = 0 1486 WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\''; 1487 $db->sql_query($sql); 1488 } 1489 } 1490 1491 1492 /** 1493 * Base user class 1494 * 1495 * This is the overarching class which contains (through session extend) 1496 * all methods utilised for user functionality during a session. 1497 * 1498 * @package phpBB3 1499 */ 1500 class user extends session 1501 { 1502 var $lang = array(); 1503 var $help = array(); 1504 var $theme = array(); 1505 var $date_format; 1506 var $timezone; 1507 var $dst; 1508 1509 var $lang_name = false; 1510 var $lang_id = false; 1511 var $lang_path; 1512 var $img_lang; 1513 var $img_array = array(); 1514 1515 // Able to add new options (up to id 31) 1516 var $keyoptions = array('viewimg' => 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17); 1517 1518 /** 1519 * Constructor to set the lang path 1520 */ 1521 function user() 1522 { 1523 global $phpbb_root_path; 1524 1525 $this->lang_path = $phpbb_root_path . 'language/'; 1526 } 1527 1528 /** 1529 * Function to set custom language path (able to use directory outside of phpBB) 1530 * 1531 * @param string $lang_path New language path used. 1532 * @access public 1533 */ 1534 function set_custom_lang_path($lang_path) 1535 { 1536 $this->lang_path = $lang_path; 1537 1538 if (substr($this->lang_path, -1) != '/') 1539 { 1540 $this->lang_path .= '/'; 1541 } 1542 } 1543 1544 /** 1545 * Setup basic user-specific items (style, language, ...) 1546 */ 1547 function setup($lang_set = false, $style = false) 1548 { 1549 global $db, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; 1550 1551 if ($this->data['user_id'] != ANONYMOUS) 1552 { 1553 $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); 1554 1555 $this->date_format = $this->data['user_dateformat']; 1556 $this->timezone = $this->data['user_timezone'] * 3600; 1557 $this->dst = $this->data['user_dst'] * 3600; 1558 } 1559 else 1560 { 1561 $this->lang_name = basename($config['default_lang']); 1562 $this->date_format = $config['default_dateformat']; 1563 $this->timezone = $config['board_timezone'] * 3600; 1564 $this->dst = $config['board_dst'] * 3600; 1565 1566 /** 1567 * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language 1568 * If re-enabled we need to make sure only those languages installed are checked 1569 * Commented out so we do not loose the code. 1570 1571 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) 1572 { 1573 $accept_lang_ary = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); 1574 1575 foreach ($accept_lang_ary as $accept_lang) 1576 { 1577 // Set correct format ... guess full xx_YY form 1578 $accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2)); 1579 $accept_lang = basename($accept_lang); 1580 1581 if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) 1582 { 1583 $this->lang_name = $config['default_lang'] = $accept_lang; 1584 break; 1585 } 1586 else 1587 { 1588 // No match on xx_YY so try xx 1589 $accept_lang = substr($accept_lang, 0, 2); 1590 $accept_lang = basename($accept_lang); 1591 1592 if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) 1593 { 1594 $this->lang_name = $config['default_lang'] = $accept_lang; 1595 break; 1596 } 1597 } 1598 } 1599 } 1600 */ 1601 } 1602 1603 // We include common language file here to not load it every time a custom language file is included 1604 $lang = &$this->lang; 1605 1606 // Do not suppress error if in DEBUG_EXTRA mode 1607 $include_result = (defined('DEBUG_EXTRA')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); 1608 1609 if ($include_result === false) 1610 { 1611 die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); 1612 } 1613 1614 $this->add_lang($lang_set); 1615 unset($lang_set); 1616 1617 if (!empty($_GET['style']) && $auth->acl_get('a_styles') && !defined('ADMIN_START')) 1618 { 1619 global $SID, $_EXTRA_URL; 1620 1621 $style = request_var('style', 0); 1622 $SID .= '&style=' . $style; 1623 $_EXTRA_URL = array('style=' . $style); 1624 } 1625 else 1626 { 1627 // Set up style 1628 $style = ($style) ? $style : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']); 1629 } 1630 1631 $sql = 'SELECT s.style_id, t.template_storedb, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name 1632 FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i 1633 WHERE s.style_id = $style 1634 AND t.template_id = s.template_id 1635 AND c.theme_id = s.theme_id 1636 AND i.imageset_id = s.imageset_id"; 1637 $result = $db->sql_query($sql, 3600); 1638 $this->theme = $db->sql_fetchrow($result); 1639 $db->sql_freeresult($result); 1640 1641 // User has wrong style 1642 if (!$this->theme && $style == $this->data['user_style']) 1643 { 1644 $style = $this->data['user_style'] = $config['default_style']; 1645 1646 $sql = 'UPDATE ' . USERS_TABLE . " 1647 SET user_style = $style 1648 WHERE user_id = {$this->data['user_id']}"; 1649 $db->sql_query($sql); 1650 1651 $sql = 'SELECT s.style_id, t.template_storedb, t.template_path, t.template_id, t.bbcode_bitfield, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name 1652 FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i 1653 WHERE s.style_id = $style 1654 AND t.template_id = s.template_id 1655 AND c.theme_id = s.theme_id 1656 AND i.imageset_id = s.imageset_id"; 1657 $result = $db->sql_query($sql, 3600); 1658 $this->theme = $db->sql_fetchrow($result); 1659 $db->sql_freeresult($result); 1660 } 1661 1662 if (!$this->theme) 1663 { 1664 trigger_error('NO_STYLE_DATA', E_USER_ERROR); 1665 } 1666 1667 // Now parse the cfg file and cache it 1668 $parsed_items = $cache->obtain_cfg_items($this->theme); 1669 1670 // We are only interested in the theme configuration for now 1671 $parsed_items = $parsed_items['theme']; 1672 1673 $check_for = array( 1674 'parse_css_file' => (int) 0, 1675 'pagination_sep' => (string) ', ' 1676 ); 1677 1678 foreach ($check_for as $key => $default_value) 1679 { 1680 $this->theme[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value; 1681 settype($this->theme[$key], gettype($default_value)); 1682 1683 if (is_string($default_value)) 1684 { 1685 $this->theme[$key] = htmlspecialchars($this->theme[$key]); 1686 } 1687 } 1688 1689 // If the style author specified the theme needs to be cached 1690 // (because of the used paths and variables) than make sure it is the case. 1691 // For example, if the theme uses language-specific images it needs to be stored in db. 1692 if (!$this->theme['theme_storedb'] && $this->theme['parse_css_file']) 1693 { 1694 $this->theme['theme_storedb'] = 1; 1695 1696 $stylesheet = file_get_contents("{$phpbb_root_path}styles/{$this->theme['theme_path']}/theme/stylesheet.css"); 1697 // Match CSS imports 1698 $matches = array(); 1699 preg_match_all('/@import url\(["\'](.*)["\']\);/i', $stylesheet, $matches); 1700 1701 if (sizeof($matches)) 1702 { 1703 $content = ''; 1704 foreach ($matches[0] as $idx => $match) 1705 { 1706 if ($content = @file_get_contents("{$phpbb_root_path}styles/{$this->theme['theme_path']}/theme/" . $matches[1][$idx])) 1707 { 1708 $content = trim($content); 1709 } 1710 else 1711 { 1712 $content = ''; 1713 } 1714 $stylesheet = str_replace($match, $content, $stylesheet); 1715 } 1716 unset($content); 1717 } 1718 1719 $stylesheet = str_replace('./', 'styles/' . $this->theme['theme_path'] . '/theme/', $stylesheet); 1720 1721 $sql_ary = array( 1722 'theme_data' => $stylesheet, 1723 'theme_mtime' => time(), 1724 'theme_storedb' => 1 1725 ); 1726 1727 $sql = 'UPDATE ' . STYLES_THEME_TABLE . ' 1728 SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' 1729 WHERE theme_id = ' . $this->theme['theme_id']; 1730 $db->sql_query($sql); 1731 1732 unset($sql_ary); 1733 } 1734 1735 $template->set_template(); 1736 1737 $this->img_lang = (file_exists($phpbb_root_path . 'styles/' . $this->theme['imageset_path'] . '/imageset/' . $this->lang_name)) ? $this->lang_name : $config['default_lang']; 1738 1739 // Same query in style.php 1740 $sql = 'SELECT * 1741 FROM ' . STYLES_IMAGESET_DATA_TABLE . ' 1742 WHERE imageset_id = ' . $this->theme['imageset_id'] . " 1743 AND image_filename <> '' 1744 AND image_lang IN ('" . $db->sql_escape($this->img_lang) . "', '')"; 1745 $result = $db->sql_query($sql, 3600); 1746 1747 $localised_images = false; 1748 while ($row = $db->sql_fetchrow($result)) 1749 { 1750 if ($row['image_lang']) 1751 { 1752 $localised_images = true; 1753 } 1754 1755 $row['image_filename'] = rawurlencode($row['image_filename']); 1756 $this->img_array[$row['image_name']] = $row; 1757 } 1758 $db->sql_freeresult($result); 1759 1760 // there were no localised images, try to refresh the localised imageset for the user's language 1761 if (!$localised_images) 1762 { 1763 // Attention: this code ignores the image definition list from acp_styles and just takes everything 1764 // that the config file contains 1765 $sql_ary = array(); 1766 1767 $db->sql_transaction('begin'); 1768 1769 $sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . ' 1770 WHERE imageset_id = ' . $this->theme['imageset_id'] . ' 1771 AND image_lang = \'' . $db->sql_escape($this->img_lang) . '\''; 1772 $result = $db->sql_query($sql); 1773 1774 if (@file_exists("{$phpbb_root_path}styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg")) 1775 { 1776 $cfg_data_imageset_data = parse_cfg_file("{$phpbb_root_path}styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg"); 1777 foreach ($cfg_data_imageset_data as $image_name => $value) 1778 { 1779 if (strpos($value, '*') !== false) 1780 { 1781 if (substr($value, -1, 1) === '*') 1782 { 1783 list($image_filename, $image_height) = explode('*', $value); 1784 $image_width = 0; 1785 } 1786 else 1787 { 1788 list($image_filename, $image_height, $image_width) = explode('*', $value); 1789 } 1790 } 1791 else 1792 { 1793 $image_filename = $value; 1794 $image_height = $image_width = 0; 1795 } 1796 1797 if (strpos($image_name, 'img_') === 0 && $image_filename) 1798 { 1799 $image_name = substr($image_name, 4); 1800 $sql_ary[] = array( 1801 'image_name' => (string) $image_name, 1802 'image_filename' => (string) $image_filename, 1803 'image_height' => (int) $image_height, 1804 'image_width' => (int) $image_width, 1805 'imageset_id' => (int) $this->theme['imageset_id'], 1806 'image_lang' => (string) $this->img_lang, 1807 ); 1808 } 1809 } 1810 } 1811 1812 if (sizeof($sql_ary)) 1813 { 1814 $db->sql_multi_insert(STYLES_IMAGESET_DATA_TABLE, $sql_ary); 1815 $db->sql_transaction('commit'); 1816 $cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); 1817 1818 add_log('admin', 'LOG_IMAGESET_LANG_REFRESHED', $this->theme['imageset_name'], $this->img_lang); 1819 } 1820 else 1821 { 1822 $db->sql_transaction('commit'); 1823 add_log('admin', 'LOG_IMAGESET_LANG_MISSING', $this->theme['imageset_name'], $this->img_lang); 1824 } 1825 } 1826 1827 // Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes... 1828 // After calling it we continue script execution... 1829 phpbb_user_session_handler(); 1830 1831 // If this function got called from the error handler we are finished here. 1832 if (defined('IN_ERROR_HANDLER')) 1833 { 1834 return; 1835 } 1836 1837 // Disable board if the install/ directory is still present 1838 // For the brave development army we do not care about this, else we need to comment out this everytime we develop locally 1839 if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) 1840 { 1841 // Adjust the message slightly according to the permissions 1842 if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) 1843 { 1844 $message = 'REMOVE_INSTALL'; 1845 } 1846 else 1847 { 1848 $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; 1849 } 1850 trigger_error($message); 1851 } 1852 1853 // Is board disabled and user not an admin or moderator? 1854 if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) 1855 { 1856 if ($this->data['is_bot']) 1857 { 1858 send_status_line(503, 'Service Unavailable'); 1859 } 1860 1861 $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; 1862 trigger_error($message); 1863 } 1864 1865 // Is load exceeded? 1866 if ($config['limit_load'] && $this->load !== false) 1867 { 1868 if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN')) 1869 { 1870 // Set board disabled to true to let the admins/mods get the proper notification 1871 $config['board_disable'] = '1'; 1872 1873 if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) 1874 { 1875 if ($this->data['is_bot']) 1876 { 1877 send_status_line(503, 'Service Unavailable'); 1878 } 1879 trigger_error('BOARD_UNAVAILABLE'); 1880 } 1881 } 1882 } 1883 1884 if (isset($this->data['session_viewonline'])) 1885 { 1886 // Make sure the user is able to hide his session 1887 if (!$this->data['session_viewonline']) 1888 { 1889 // Reset online status if not allowed to hide the session... 1890 if (!$auth->acl_get('u_hideonline')) 1891 { 1892 $sql = 'UPDATE ' . SESSIONS_TABLE . ' 1893 SET session_viewonline = 1 1894 WHERE session_user_id = ' . $this->data['user_id']; 1895 $db->sql_query($sql); 1896 $this->data['session_viewonline'] = 1; 1897 } 1898 } 1899 else if (!$this->data['user_allow_viewonline']) 1900 { 1901 // the user wants to hide and is allowed to -> cloaking device on. 1902 if ($auth->acl_get('u_hideonline')) 1903 { 1904 $sql = 'UPDATE ' . SESSIONS_TABLE . ' 1905 SET session_viewonline = 0 1906 WHERE session_user_id = ' . $this->data['user_id']; 1907 $db->sql_query($sql); 1908 $this->data['session_viewonline'] = 0; 1909 } 1910 } 1911 } 1912 1913 1914 // Does the user need to change their password? If so, redirect to the 1915 // ucp profile reg_details page ... of course do not redirect if we're already in the ucp 1916 if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) 1917 { 1918 if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx") 1919 { 1920 redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&mode=reg_details')); 1921 } 1922 } 1923 1924 return; 1925 } 1926 1927 /** 1928 * More advanced language substitution 1929 * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. 1930 * Params are the language key and the parameters to be substituted. 1931 * This function/functionality is inspired by SHS` and Ashe. 1932 * 1933 * Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp> 1934 */ 1935 function lang() 1936 { 1937 $args = func_get_args(); 1938 $key = $args[0]; 1939 1940 if (is_array($key)) 1941 { 1942 $lang = &$this->lang[array_shift($key)]; 1943 1944 foreach ($key as $_key) 1945 { 1946 $lang = &$lang[$_key]; 1947 } 1948 } 1949 else 1950 { 1951 $lang = &$this->lang[$key]; 1952 } 1953 1954 // Return if language string does not exist 1955 if (!isset($lang) || (!is_string($lang) && !is_array($lang))) 1956 { 1957 return $key; 1958 } 1959 1960 // If the language entry is a string, we simply mimic sprintf() behaviour 1961 if (is_string($lang)) 1962 { 1963 if (sizeof($args) == 1) 1964 { 1965 return $lang; 1966 } 1967 1968 // Replace key with language entry and simply pass along... 1969 $args[0] = $lang; 1970 return call_user_func_array('sprintf', $args); 1971 } 1972 1973 // It is an array... now handle different nullar/singular/plural forms 1974 $key_found = false; 1975 1976 // We now get the first number passed and will select the key based upon this number 1977 for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) 1978 { 1979 if (is_int($args[$i])) 1980 { 1981 $numbers = array_keys($lang); 1982 1983 foreach ($numbers as $num) 1984 { 1985 if ($num > $args[$i]) 1986 { 1987 break; 1988 } 1989 1990 $key_found = $num; 1991 } 1992 break; 1993 } 1994 } 1995 1996 // Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) 1997 if ($key_found === false) 1998 { 1999 $numbers = array_keys($lang); 2000 $key_found = end($numbers); 2001 } 2002 2003 // Use the language string we determined and pass it to sprintf() 2004 $args[0] = $lang[$key_found]; 2005 return call_user_func_array('sprintf', $args); 2006 } 2007 2008 /** 2009 * Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion) 2010 * 2011 * @param mixed $lang_set specifies the language entries to include 2012 * @param bool $use_db internal variable for recursion, do not use 2013 * @param bool $use_help internal variable for recursion, do not use 2014 * 2015 * Examples: 2016 * <code> 2017 * $lang_set = array('posting', 'help' => 'faq'); 2018 * $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq')) 2019 * $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq')) 2020 * $lang_set = 'posting' 2021 * $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting')) 2022 * </code> 2023 */ 2024 function add_lang($lang_set, $use_db = false, $use_help = false) 2025 { 2026 global $phpEx; 2027 2028 if (is_array($lang_set)) 2029 { 2030 foreach ($lang_set as $key => $lang_file) 2031 { 2032 // Please do not delete this line. 2033 // We have to force the type here, else [array] language inclusion will not work 2034 $key = (string) $key; 2035 2036 if ($key == 'db') 2037 { 2038 $this->add_lang($lang_file, true, $use_help); 2039 } 2040 else if ($key == 'help') 2041 { 2042 $this->add_lang($lang_file, $use_db, true); 2043 } 2044 else if (!is_array($lang_file)) 2045 { 2046 $this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help); 2047 } 2048 else 2049 { 2050 $this->add_lang($lang_file, $use_db, $use_help); 2051 } 2052 } 2053 unset($lang_set); 2054 } 2055 else if ($lang_set) 2056 { 2057 $this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help); 2058 } 2059 } 2060 2061 /** 2062 * Set language entry (called by add_lang) 2063 * @access private 2064 */ 2065 function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false) 2066 { 2067 global $phpEx; 2068 2069 // Make sure the language name is set (if the user setup did not happen it is not set) 2070 if (!$this->lang_name) 2071 { 2072 global $config; 2073 $this->lang_name = basename($config['default_lang']); 2074 } 2075 2076 // $lang == $this->lang 2077 // $help == $this->help 2078 // - add appropriate variables here, name them as they are used within the language file... 2079 if (!$use_db) 2080 { 2081 if ($use_help && strpos($lang_file, '/') !== false) 2082 { 2083 $language_filename = $this->lang_path . $this->lang_name . '/' . substr($lang_file, 0, stripos($lang_file, '/') + 1) . 'help_' . substr($lang_file, stripos($lang_file, '/') + 1) . '.' . $phpEx; 2084 } 2085 else 2086 { 2087 $language_filename = $this->lang_path . $this->lang_name . '/' . (($use_help) ? 'help_' : '') . $lang_file . '.' . $phpEx; 2088 } 2089 2090 if (!file_exists($language_filename)) 2091 { 2092 global $config; 2093 2094 if ($this->lang_name == 'en') 2095 { 2096 // The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. 2097 $language_filename = str_replace($this->lang_path . 'en', $this->lang_path . $this->data['user_lang'], $language_filename); 2098 trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); 2099 } 2100 else if ($this->lang_name == basename($config['default_lang'])) 2101 { 2102 // Fall back to the English Language 2103 $this->lang_name = 'en'; 2104 $this->set_lang($lang, $help, $lang_file, $use_db, $use_help); 2105 } 2106 else if ($this->lang_name == $this->data['user_lang']) 2107 { 2108 // Fall back to the board default language 2109 $this->lang_name = basename($config['default_lang']); 2110 $this->set_lang($lang, $help, $lang_file, $use_db, $use_help); 2111 } 2112 2113 // Reset the lang name 2114 $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); 2115 return; 2116 } 2117 2118 // Do not suppress error if in DEBUG_EXTRA mode 2119 $include_result = (defined('DEBUG_EXTRA')) ? (include $language_filename) : (@include $language_filename); 2120 2121 if ($include_result === false) 2122 { 2123 trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); 2124 } 2125 } 2126 else if ($use_db) 2127 { 2128 // Get Database Language Strings 2129 // Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed 2130 // For example: help:faq, posting 2131 } 2132 } 2133 2134 /** 2135 * Format user date 2136 * 2137 * @param int $gmepoch unix timestamp 2138 * @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i. 2139 * @param bool $forcedate force non-relative date format. 2140 * 2141 * @return mixed translated date 2142 */ 2143 function format_date($gmepoch, $format = false, $forcedate = false) 2144 { 2145 static $midnight; 2146 static $date_cache; 2147 2148 $format = (!$format) ? $this->date_format : $format; 2149 $now = time(); 2150 $delta = $now - $gmepoch; 2151 2152 if (!isset($date_cache[$format])) 2153 { 2154 // Is the user requesting a friendly date format (i.e. 'Today 12:42')? 2155 $date_cache[$format] = array( 2156 'is_short' => strpos($format, '|'), 2157 'format_short' => substr($format, 0, strpos($format, '|')) . '||' . substr(strrchr($format, '|'), 1), 2158 'format_long' => str_replace('|', '', $format), 2159 // Filter out values that are not strings (e.g. arrays) for strtr(). 2160 'lang' => array_filter($this->lang['datetime'], 'is_string'), 2161 ); 2162 2163 // Short representation of month in format? Some languages use different terms for the long and short format of May 2164 if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) 2165 { 2166 $date_cache[$format]['lang']['May'] = $this->lang['datetime']['May_short']; 2167 } 2168 } 2169 2170 // Zone offset 2171 $zone_offset = $this->timezone + $this->dst; 2172 2173 // Show date <= 1 hour ago as 'xx min ago' but not greater than 60 seconds in the future 2174 // A small tolerence is given for times in the future but in the same minute are displayed as '< than a minute ago' 2175 if ($delta <= 3600 && $delta > -60 && ($delta >= -5 || (($now / 60) % 60) == (($gmepoch / 60) % 60)) && $date_cache[$format]['is_short'] !== false && !$forcedate && isset($this->lang['datetime']['AGO'])) 2176 { 2177 return $this->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); 2178 } 2179 2180 if (!$midnight) 2181 { 2182 list($d, $m, $y) = explode(' ', gmdate('j n Y', time() + $zone_offset)); 2183 $midnight = gmmktime(0, 0, 0, $m, $d, $y) - $zone_offset; 2184 } 2185 2186 if ($date_cache[$format]['is_short'] !== false && !$forcedate && !($gmepoch < $midnight - 86400 || $gmepoch > $midnight + 172800)) 2187 { 2188 $day = false; 2189 2190 if ($gmepoch > $midnight + 86400) 2191 { 2192 $day = 'TOMORROW'; 2193 } 2194 else if ($gmepoch > $midnight) 2195 { 2196 $day = 'TODAY'; 2197 } 2198 else if ($gmepoch > $midnight - 86400) 2199 { 2200 $day = 'YESTERDAY'; 2201 } 2202 2203 if ($day !== false) 2204 { 2205 return str_replace('||', $this->lang['datetime'][$day], strtr(@gmdate($date_cache[$format]['format_short'], $gmepoch + $zone_offset), $date_cache[$format]['lang'])); 2206 } 2207 } 2208 2209 return strtr(@gmdate($date_cache[$format]['format_long'], $gmepoch + $zone_offset), $date_cache[$format]['lang']); 2210 } 2211 2212 /** 2213 * Get language id currently used by the user 2214 */ 2215 function get_iso_lang_id() 2216 { 2217 global $config, $db; 2218 2219 if (!empty($this->lang_id)) 2220 { 2221 return $this->lang_id; 2222 } 2223 2224 if (!$this->lang_name) 2225 { 2226 $this->lang_name = $config['default_lang']; 2227 } 2228 2229 $sql = 'SELECT lang_id 2230 FROM ' . LANG_TABLE . " 2231 WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'"; 2232 $result = $db->sql_query($sql); 2233 $this->lang_id = (int) $db->sql_fetchfield('lang_id'); 2234 $db->sql_freeresult($result); 2235 2236 return $this->lang_id; 2237 } 2238 2239 /** 2240 * Get users profile fields 2241 */ 2242 function get_profile_fields($user_id) 2243 { 2244 global $db; 2245 2246 if (isset($this->profile_fields)) 2247 { 2248 return; 2249 } 2250 2251 $sql = 'SELECT * 2252 FROM ' . PROFILE_FIELDS_DATA_TABLE . " 2253 WHERE user_id = $user_id"; 2254 $result = $db->sql_query_limit($sql, 1); 2255 $this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row; 2256 $db->sql_freeresult($result); 2257 } 2258 2259 /** 2260 * Specify/Get image 2261 * $suffix is no longer used - we know it. ;) It is there for backward compatibility. 2262 */ 2263 function img($img, $alt = '', $width = false, $suffix = '', $type = 'full_tag') 2264 { 2265 static $imgs; 2266 global $phpbb_root_path; 2267 2268 $img_data = &$imgs[$img]; 2269 2270 if (empty($img_data)) 2271 { 2272 if (!isset($this->img_array[$img])) 2273 { 2274 // Do not fill the image to let designers decide what to do if the image is empty 2275 $img_data = ''; 2276 return $img_data; 2277 } 2278 2279 // Use URL if told so 2280 $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; 2281 2282 $path = 'styles/' . rawurlencode($this->theme['imageset_path']) . '/imageset/' . ($this->img_array[$img]['image_lang'] ? $this->img_array[$img]['image_lang'] .'/' : '') . $this->img_array[$img]['image_filename']; 2283 2284 $img_data['src'] = $root_path . $path; 2285 $img_data['width'] = $this->img_array[$img]['image_width']; 2286 $img_data['height'] = $this->img_array[$img]['image_height']; 2287 2288 // We overwrite the width and height to the phpbb logo's width 2289 // and height here if the contents of the site_logo file are 2290 // really equal to the phpbb_logo 2291 // This allows us to change the dimensions of the phpbb_logo without 2292 // modifying the imageset.cfg and causing a conflict for everyone 2293 // who modified it for their custom logo on updating 2294 if ($img == 'site_logo' && file_exists($phpbb_root_path . $path)) 2295 { 2296 global $cache; 2297 2298 $img_file_hashes = $cache->get('imageset_site_logo_md5'); 2299 2300 if ($img_file_hashes === false) 2301 { 2302 $img_file_hashes = array(); 2303 } 2304 2305 $key = $this->theme['imageset_path'] . '::' . $this->img_array[$img]['image_lang']; 2306 if (!isset($img_file_hashes[$key])) 2307 { 2308 $img_file_hashes[$key] = md5(file_get_contents($phpbb_root_path . $path)); 2309 $cache->put('imageset_site_logo_md5', $img_file_hashes); 2310 } 2311 2312 $phpbb_logo_hash = '0c461a32cd3621643105f0d02a772c10'; 2313 2314 if ($phpbb_logo_hash == $img_file_hashes[$key]) 2315 { 2316 $img_data['width'] = '149'; 2317 $img_data['height'] = '52'; 2318 } 2319 } 2320 } 2321 2322 $alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt; 2323 2324 switch ($type) 2325 { 2326 case 'src': 2327 return $img_data['src']; 2328 break; 2329 2330 case 'width': 2331 return ($width === false) ? $img_data['width'] : $width; 2332 break; 2333 2334 case 'height': 2335 return $img_data['height']; 2336 break; 2337 2338 default: 2339 $use_width = ($width === false) ? $img_data['width'] : $width; 2340 2341 return '<img src="' . $img_data['src'] . '"' . (($use_width) ? ' width="' . $use_width . '"' : '') . (($img_data['height']) ? ' height="' . $img_data['height'] . '"' : '') . ' alt="' . $alt . '" title="' . $alt . '" />'; 2342 break; 2343 } 2344 } 2345 2346 /** 2347 * Get option bit field from user options. 2348 * 2349 * @param int $key option key, as defined in $keyoptions property. 2350 * @param int $data bit field value to use, or false to use $this->data['user_options'] 2351 * @return bool true if the option is set in the bit field, false otherwise 2352 */ 2353 function optionget($key, $data = false) 2354 { 2355 $var = ($data !== false) ? $data : $this->data['user_options']; 2356 return phpbb_optionget($this->keyoptions[$key], $var); 2357 } 2358 2359 /** 2360 * Set option bit field for user options. 2361 * 2362 * @param int $key Option key, as defined in $keyoptions property. 2363 * @param bool $value True to set the option, false to clear the option. 2364 * @param int $data Current bit field value, or false to use $this->data['user_options'] 2365 * @return int|bool If $data is false, the bit field is modified and 2366 * written back to $this->data['user_options'], and 2367 * return value is true if the bit field changed and 2368 * false otherwise. If $data is not false, the new 2369 * bitfield value is returned. 2370 */ 2371 function optionset($key, $value, $data = false) 2372 { 2373 $var = ($data !== false) ? $data : $this->data['user_options']; 2374 2375 $new_var = phpbb_optionset($this->keyoptions[$key], $value, $var); 2376 2377 if ($data === false) 2378 { 2379 if ($new_var != $var) 2380 { 2381 $this->data['user_options'] = $new_var; 2382 return true; 2383 } 2384 else 2385 { 2386 return false; 2387 } 2388 } 2389 else 2390 { 2391 return $new_var; 2392 } 2393 } 2394 2395 /** 2396 * Funtion to make the user leave the NEWLY_REGISTERED system group. 2397 * @access public 2398 */ 2399 function leave_newly_registered() 2400 { 2401 global $db; 2402 2403 if (empty($this->data['user_new'])) 2404 { 2405 return false; 2406 } 2407 2408 if (!function_exists('remove_newly_registered')) 2409 { 2410 global $phpbb_root_path, $phpEx; 2411 2412 include($phpbb_root_path . 'includes/functions_user.' . $phpEx); 2413 } 2414 if ($group = remove_newly_registered($this->data['user_id'], $this->data)) 2415 { 2416 $this->data['group_id'] = $group; 2417 2418 } 2419 $this->data['user_permissions'] = ''; 2420 $this->data['user_new'] = 0; 2421 2422 return true; 2423 } 2424 2425 /** 2426 * Returns all password protected forum ids the user is currently NOT authenticated for. 2427 * 2428 * @return array Array of forum ids 2429 * @access public 2430 */ 2431 function get_passworded_forums() 2432 { 2433 global $db; 2434 2435 $sql = 'SELECT f.forum_id, fa.user_id 2436 FROM ' . FORUMS_TABLE . ' f 2437 LEFT JOIN ' . FORUMS_ACCESS_TABLE . " fa 2438 ON (fa.forum_id = f.forum_id 2439 AND fa.session_id = '" . $db->sql_escape($this->session_id) . "') 2440 WHERE f.forum_password <> ''"; 2441 $result = $db->sql_query($sql); 2442 2443 $forum_ids = array(); 2444 while ($row = $db->sql_fetchrow($result)) 2445 { 2446 $forum_id = (int) $row['forum_id']; 2447 2448 if ($row['user_id'] != $this->data['user_id']) 2449 { 2450 $forum_ids[$forum_id] = $forum_id; 2451 } 2452 } 2453 $db->sql_freeresult($result); 2454 2455 return $forum_ids; 2456 } 2457 } 2458 2459 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Oct 2 15:03:47 2013 | Cross-referenced by PHPXref 0.7.1 |