[ Index ]

PHP Cross Reference of phpBB-3.3.10-deutsch

title

Body

[close]

/phpbb/ -> session.php (source)

   1  <?php
   2  /**
   3  *
   4  * This file is part of the phpBB Forum Software package.
   5  *
   6  * @copyright (c) phpBB Limited <https://www.phpbb.com>
   7  * @license GNU General Public License, version 2 (GPL-2.0)
   8  *
   9  * For full copyright and license information, please see
  10  * the docs/CREDITS.txt file.
  11  *
  12  */
  13  
  14  namespace phpbb;
  15  
  16  /**
  17  * Session class
  18  */
  19  class session
  20  {
  21      var $cookie_data = array();
  22      var $page = array();
  23      var $data = array();
  24      var $browser = '';
  25      var $forwarded_for = '';
  26      var $host = '';
  27      var $session_id = '';
  28      var $ip = '';
  29      var $load = 0;
  30      var $time_now = 0;
  31      var $update_session_page = true;
  32  
  33      /**
  34       * Extract current session page
  35       *
  36       * @param string $root_path current root path (phpbb_root_path)
  37       * @return array
  38       */
  39  	static function extract_current_page($root_path)
  40      {
  41          global $request, $symfony_request, $phpbb_filesystem;
  42  
  43          $page_array = array();
  44  
  45          // First of all, get the request uri...
  46          $script_name = $request->escape($symfony_request->getScriptName(), true);
  47          $args = $request->escape(explode('&', $symfony_request->getQueryString()), true);
  48  
  49          // 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...
  50          if (!$script_name)
  51          {
  52              $script_name = html_entity_decode($request->server('REQUEST_URI'), ENT_COMPAT);
  53              $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name;
  54              $page_array['failover'] = 1;
  55          }
  56  
  57          // Replace backslashes and doubled slashes (could happen on some proxy setups)
  58          $script_name = str_replace(array('\\', '//'), '/', $script_name);
  59  
  60          // Now, remove the sid and let us get a clean query string...
  61          $use_args = array();
  62  
  63          // Since some browser do not encode correctly we need to do this with some "special" characters...
  64          // " -> %22, ' => %27, < -> %3C, > -> %3E
  65          $find = array('"', "'", '<', '>', '&quot;', '&lt;', '&gt;');
  66          $replace = array('%22', '%27', '%3C', '%3E', '%22', '%3C', '%3E');
  67  
  68          foreach ($args as $key => $argument)
  69          {
  70              if (strpos($argument, 'sid=') === 0)
  71              {
  72                  continue;
  73              }
  74  
  75              $use_args[] = str_replace($find, $replace, $argument);
  76          }
  77          unset($args);
  78  
  79          // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2
  80  
  81          // The current query string
  82          $query_string = trim(implode('&', $use_args));
  83  
  84          // basenamed page name (for example: index.php)
  85          $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name);
  86          $page_name = urlencode(htmlspecialchars($page_name, ENT_COMPAT));
  87  
  88          $symfony_request_path = $phpbb_filesystem->clean_path($symfony_request->getPathInfo());
  89          if ($symfony_request_path !== '/')
  90          {
  91              $page_name .= str_replace('%2F', '/', urlencode($symfony_request_path));
  92          }
  93  
  94          if (substr($root_path, 0, 2) === './' && strpos($root_path, '..') === false)
  95          {
  96              $root_dirs = explode('/', str_replace('\\', '/', rtrim($root_path, '/')));
  97              $page_dirs = explode('/', str_replace('\\', '/', '.'));
  98          }
  99          else
 100          {
 101              // current directory within the phpBB root (for example: adm)
 102              $root_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath($root_path)));
 103              $page_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath('./')));
 104          }
 105  
 106          $intersection = array_intersect_assoc($root_dirs, $page_dirs);
 107  
 108          $root_dirs = array_diff_assoc($root_dirs, $intersection);
 109          $page_dirs = array_diff_assoc($page_dirs, $intersection);
 110  
 111          $page_dir = str_repeat('../', count($root_dirs)) . implode('/', $page_dirs);
 112  
 113          if ($page_dir && substr($page_dir, -1, 1) == '/')
 114          {
 115              $page_dir = substr($page_dir, 0, -1);
 116          }
 117  
 118          // Current page from phpBB root (for example: adm/index.php?i=10&b=2)
 119          $page = (($page_dir) ? $page_dir . '/' : '') . $page_name;
 120          if ($query_string)
 121          {
 122              $page .= '?' . $query_string;
 123          }
 124  
 125          // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in /
 126          $script_path = $symfony_request->getBasePath();
 127  
 128          // The script path from the webroot to the phpBB root (for example: /phpBB3/)
 129          $script_dirs = explode('/', $script_path);
 130          array_splice($script_dirs, -count($page_dirs));
 131          $root_script_path = implode('/', $script_dirs) . (count($root_dirs) ? '/' . implode('/', $root_dirs) : '');
 132  
 133          // We are on the base level (phpBB root == webroot), lets adjust the variables a bit...
 134          if (!$root_script_path)
 135          {
 136              $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path;
 137          }
 138  
 139          $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/';
 140          $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/';
 141  
 142          $forum_id = $request->variable('f', 0);
 143          // maximum forum id value is maximum value of mediumint unsigned column
 144          $forum_id = ($forum_id > 0 && $forum_id < 16777215) ? $forum_id : 0;
 145  
 146          $page_array += array(
 147              'page_name'            => $page_name,
 148              'page_dir'            => $page_dir,
 149  
 150              'query_string'        => $query_string,
 151              'script_path'        => str_replace(' ', '%20', htmlspecialchars($script_path, ENT_COMPAT)),
 152              'root_script_path'    => str_replace(' ', '%20', htmlspecialchars($root_script_path, ENT_COMPAT)),
 153  
 154              'page'                => $page,
 155              'forum'                => $forum_id,
 156          );
 157  
 158          return $page_array;
 159      }
 160  
 161      /**
 162      * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present.
 163      */
 164  	function extract_current_hostname()
 165      {
 166          global $config, $request;
 167  
 168          // Get hostname
 169          $host = html_entity_decode($request->header('Host', $request->server('SERVER_NAME')), ENT_COMPAT);
 170  
 171          // Should be a string and lowered
 172          $host = (string) strtolower($host);
 173  
 174          // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid
 175          if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name']))
 176          {
 177              return $host;
 178          }
 179  
 180          // Is the host actually a IP? If so, we use the IP... (IPv4)
 181          if (long2ip(ip2long($host)) === $host)
 182          {
 183              return $host;
 184          }
 185  
 186          // 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
 187          $host = @parse_url('http://' . $host);
 188          $host = (!empty($host['host'])) ? $host['host'] : '';
 189  
 190          // Remove any portions not removed by parse_url (#)
 191          $host = str_replace('#', '', $host);
 192  
 193          // If, by any means, the host is now empty, we will use a "best approach" way to guess one
 194          if (empty($host))
 195          {
 196              if (!empty($config['server_name']))
 197              {
 198                  $host = $config['server_name'];
 199              }
 200              else if (!empty($config['cookie_domain']))
 201              {
 202                  $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain'];
 203              }
 204              else
 205              {
 206                  // Set to OS hostname or localhost
 207                  $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost';
 208              }
 209          }
 210  
 211          // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set)
 212          return $host;
 213      }
 214  
 215      /**
 216      * Start session management
 217      *
 218      * This is where all session activity begins. We gather various pieces of
 219      * information from the client and server. We test to see if a session already
 220      * exists. If it does, fine and dandy. If it doesn't we'll go on to create a
 221      * new one ... pretty logical heh? We also examine the system load (if we're
 222      * running on a system which makes such information readily available) and
 223      * halt if it's above an admin definable limit.
 224      *
 225      * @param bool $update_session_page if true the session page gets updated.
 226      *            This can be set to circumvent certain scripts to update the users last visited page.
 227      */
 228  	function session_begin($update_session_page = true)
 229      {
 230          global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path;
 231          global $request, $phpbb_container, $user, $phpbb_log, $phpbb_dispatcher;
 232  
 233          // Give us some basic information
 234          $this->time_now                = time();
 235          $this->cookie_data            = array('u' => 0, 'k' => '');
 236          $this->update_session_page    = $update_session_page;
 237          $this->browser                = $request->header('User-Agent');
 238          $this->referer                = $request->header('Referer');
 239          $this->forwarded_for        = $request->header('X-Forwarded-For');
 240  
 241          $this->host                    = $this->extract_current_hostname();
 242          $this->page                    = $this->extract_current_page($phpbb_root_path);
 243  
 244          // if the forwarded for header shall be checked we have to validate its contents
 245          if ($config['forwarded_for_check'])
 246          {
 247              $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for));
 248  
 249              // split the list of IPs
 250              $ips = explode(' ', $this->forwarded_for);
 251              foreach ($ips as $ip)
 252              {
 253                  if (!filter_var($ip, FILTER_VALIDATE_IP))
 254                  {
 255                      // contains invalid data, don't use the forwarded for header
 256                      $this->forwarded_for = '';
 257                      break;
 258                  }
 259              }
 260          }
 261          else
 262          {
 263              $this->forwarded_for = '';
 264          }
 265  
 266          if ($request->is_set($config['cookie_name'] . '_sid', \phpbb\request\request_interface::COOKIE) || $request->is_set($config['cookie_name'] . '_u', \phpbb\request\request_interface::COOKIE))
 267          {
 268              $this->cookie_data['u'] = $request->variable($config['cookie_name'] . '_u', 0, false, \phpbb\request\request_interface::COOKIE);
 269              $this->cookie_data['k'] = $request->variable($config['cookie_name'] . '_k', '', false, \phpbb\request\request_interface::COOKIE);
 270              $this->session_id         = $request->variable($config['cookie_name'] . '_sid', '', false, \phpbb\request\request_interface::COOKIE);
 271  
 272              $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid=';
 273              $_SID = (defined('NEED_SID')) ? $this->session_id : '';
 274  
 275              if (empty($this->session_id))
 276              {
 277                  $this->session_id = $_SID = $request->variable('sid', '');
 278                  $SID = '?sid=' . $this->session_id;
 279                  $this->cookie_data = array('u' => 0, 'k' => '');
 280              }
 281          }
 282          else
 283          {
 284              $this->session_id = $_SID = $request->variable('sid', '');
 285              $SID = '?sid=' . $this->session_id;
 286          }
 287  
 288          $_EXTRA_URL = array();
 289  
 290          // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests
 291          // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip.
 292          $ip = html_entity_decode($request->server('REMOTE_ADDR'), ENT_COMPAT);
 293          $ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $ip));
 294  
 295          /**
 296          * Event to alter user IP address
 297          *
 298          * @event core.session_ip_after
 299          * @var    string    ip    REMOTE_ADDR
 300          * @since 3.1.10-RC1
 301          */
 302          $vars = array('ip');
 303          extract($phpbb_dispatcher->trigger_event('core.session_ip_after', compact($vars)));
 304  
 305          // split the list of IPs
 306          $ips = explode(' ', trim($ip));
 307  
 308          // Default IP if REMOTE_ADDR is invalid
 309          $this->ip = '127.0.0.1';
 310  
 311          foreach ($ips as $ip)
 312          {
 313              // Normalise IP address
 314              $ip = phpbb_ip_normalise($ip);
 315  
 316              if ($ip === false)
 317              {
 318                  // IP address is invalid.
 319                  break;
 320              }
 321  
 322              // IP address is valid.
 323              $this->ip = $ip;
 324          }
 325  
 326          $this->load = false;
 327  
 328          // Load limit check (if applicable)
 329          if ($config['limit_load'] || $config['limit_search_load'])
 330          {
 331              if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg'))))
 332              {
 333                  $this->load = array_slice($load, 0, 1);
 334                  $this->load = floatval($this->load[0]);
 335              }
 336              else
 337              {
 338                  $config->set('limit_load', '0');
 339                  $config->set('limit_search_load', '0');
 340              }
 341          }
 342  
 343          // if no session id is set, redirect to index.php
 344          $session_id = $request->variable('sid', '');
 345          if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id))
 346          {
 347              send_status_line(401, 'Unauthorized');
 348              redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
 349          }
 350  
 351          // if session id is set
 352          if (!empty($this->session_id))
 353          {
 354              $sql = 'SELECT u.*, s.*
 355                  FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u
 356                  WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "'
 357                      AND u.user_id = s.session_user_id";
 358              $result = $db->sql_query($sql);
 359              $this->data = $db->sql_fetchrow($result);
 360              $db->sql_freeresult($result);
 361  
 362              // Did the session exist in the DB?
 363              if (isset($this->data['user_id']))
 364              {
 365                  // Validate IP length according to admin ... enforces an IP
 366                  // check on bots if admin requires this
 367  //                $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check'];
 368  
 369                  if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
 370                  {
 371                      $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
 372                      $u_ip = short_ipv6($this->ip, $config['ip_check']);
 373                  }
 374                  else
 375                  {
 376                      $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
 377                      $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
 378                  }
 379  
 380                  $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
 381                  $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
 382  
 383                  $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
 384                  $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
 385  
 386                  // referer checks
 387                  // The @ before $config['referer_validation'] suppresses notices present while running the updater
 388                  $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH);
 389                  $referer_valid = true;
 390  
 391                  // we assume HEAD and TRACE to be foul play and thus only whitelist GET
 392                  if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get')
 393                  {
 394                      $referer_valid = $this->validate_referer($check_referer_path);
 395                  }
 396  
 397                  if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid)
 398                  {
 399                      $session_expired = false;
 400  
 401                      // Check whether the session is still valid if we have one
 402                      /* @var $provider_collection \phpbb\auth\provider_collection */
 403                      $provider_collection = $phpbb_container->get('auth.provider_collection');
 404                      $provider = $provider_collection->get_provider();
 405  
 406                      if (!($provider instanceof \phpbb\auth\provider\provider_interface))
 407                      {
 408                          throw new \RuntimeException($provider . ' must implement \phpbb\auth\provider\provider_interface');
 409                      }
 410  
 411                      $ret = $provider->validate_session($this->data);
 412                      if ($ret !== null && !$ret)
 413                      {
 414                          $session_expired = true;
 415                      }
 416  
 417                      if (!$session_expired)
 418                      {
 419                          // Check the session length timeframe if autologin is not enabled.
 420                          // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide.
 421                          if (!$this->data['session_autologin'])
 422                          {
 423                              if ($this->data['session_time'] < $this->time_now - ((int) $config['session_length'] + 60))
 424                              {
 425                                  $session_expired = true;
 426                              }
 427                          }
 428                          else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60))
 429                          {
 430                              $session_expired = true;
 431                          }
 432                      }
 433  
 434                      if (!$session_expired)
 435                      {
 436                          $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
 437                          $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false;
 438                          $this->data['user_lang'] = basename($this->data['user_lang']);
 439  
 440                          // Is user banned? Are they excluded? Won't return on ban, exists within method
 441                          $this->check_ban_for_current_session($config);
 442  
 443                          return true;
 444                      }
 445                  }
 446                  else
 447                  {
 448                      // Added logging temporarily to help debug bugs...
 449                      if ($phpbb_container->getParameter('session.log_errors') && $this->data['user_id'] != ANONYMOUS)
 450                      {
 451                          if ($referer_valid)
 452                          {
 453                              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IP_BROWSER_FORWARDED_CHECK', false, array(
 454                                  $u_ip,
 455                                  $s_ip,
 456                                  $u_browser,
 457                                  $s_browser,
 458                                  htmlspecialchars($u_forwarded_for, ENT_COMPAT),
 459                                  htmlspecialchars($s_forwarded_for, ENT_COMPAT)
 460                              ));
 461                          }
 462                          else
 463                          {
 464                              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_REFERER_INVALID', false, array($this->referer));
 465                          }
 466                      }
 467                  }
 468              }
 469          }
 470  
 471          // If we reach here then no (valid) session exists. So we'll create a new one
 472          return $this->session_create();
 473      }
 474  
 475      /**
 476      * Create a new session
 477      *
 478      * If upon trying to start a session we discover there is nothing existing we
 479      * jump here. Additionally this method is called directly during login to regenerate
 480      * the session for the specific user. In this method we carry out a number of tasks;
 481      * garbage collection, (search)bot checking, banned user comparison. Basically
 482      * though this method will result in a new session for a specific user.
 483      */
 484  	function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true)
 485      {
 486          global $SID, $_SID, $db, $config, $cache, $phpbb_container, $phpbb_dispatcher;
 487  
 488          $this->data = array();
 489  
 490          /* Garbage collection ... remove old sessions updating user information
 491          // if necessary. It means (potentially) 11 queries but only infrequently
 492          if ($this->time_now > $config['session_last_gc'] + $config['session_gc'])
 493          {
 494              $this->session_gc();
 495          }*/
 496  
 497          // Do we allow autologin on this board? No? Then override anything
 498          // that may be requested here
 499          if (!$config['allow_autologin'])
 500          {
 501              $this->cookie_data['k'] = $persist_login = false;
 502          }
 503  
 504          /**
 505          * Here we do a bot check, oh er saucy! No, not that kind of bot
 506          * check. We loop through the list of bots defined by the admin and
 507          * see if we have any useragent and/or IP matches. If we do, this is a
 508          * bot, act accordingly
 509          */
 510          $bot = false;
 511          $active_bots = $cache->obtain_bots();
 512  
 513          foreach ($active_bots as $row)
 514          {
 515              if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser))
 516              {
 517                  $bot = $row['user_id'];
 518              }
 519  
 520              // If ip is supplied, we will make sure the ip is matching too...
 521              if ($row['bot_ip'] && ($bot || !$row['bot_agent']))
 522              {
 523                  // Set bot to false, then we only have to set it to true if it is matching
 524                  $bot = false;
 525  
 526                  foreach (explode(',', $row['bot_ip']) as $bot_ip)
 527                  {
 528                      $bot_ip = trim($bot_ip);
 529  
 530                      if (!$bot_ip)
 531                      {
 532                          continue;
 533                      }
 534  
 535                      if (strpos($this->ip, $bot_ip) === 0)
 536                      {
 537                          $bot = (int) $row['user_id'];
 538                          break;
 539                      }
 540                  }
 541              }
 542  
 543              if ($bot)
 544              {
 545                  break;
 546              }
 547          }
 548  
 549          /* @var $provider_collection \phpbb\auth\provider_collection */
 550          $provider_collection = $phpbb_container->get('auth.provider_collection');
 551          $provider = $provider_collection->get_provider();
 552          $this->data = $provider->autologin();
 553  
 554          if ($user_id !== false && isset($this->data['user_id']) && $this->data['user_id'] != $user_id)
 555          {
 556              $this->data = array();
 557          }
 558  
 559          if (isset($this->data['user_id']))
 560          {
 561              $this->cookie_data['k'] = '';
 562              $this->cookie_data['u'] = $this->data['user_id'];
 563          }
 564  
 565          // If we're presented with an autologin key we'll join against it.
 566          // Else if we've been passed a user_id we'll grab data based on that
 567          if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && empty($this->data))
 568          {
 569              $sql = 'SELECT u.*
 570                  FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k
 571                  WHERE u.user_id = ' . (int) $this->cookie_data['u'] . '
 572                      AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ")
 573                      AND k.user_id = u.user_id
 574                      AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
 575              $result = $db->sql_query($sql);
 576              $user_data = $db->sql_fetchrow($result);
 577  
 578              if ($user_id === false || (isset($user_data['user_id']) && $user_id == $user_data['user_id']))
 579              {
 580                  $this->data = $user_data;
 581                  $bot = false;
 582              }
 583  
 584              $db->sql_freeresult($result);
 585          }
 586  
 587          if ($user_id !== false && empty($this->data))
 588          {
 589              $this->cookie_data['k'] = '';
 590              $this->cookie_data['u'] = $user_id;
 591  
 592              $sql = 'SELECT *
 593                  FROM ' . USERS_TABLE . '
 594                  WHERE user_id = ' . (int) $this->cookie_data['u'] . '
 595                      AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')';
 596              $result = $db->sql_query($sql);
 597              $this->data = $db->sql_fetchrow($result);
 598              $db->sql_freeresult($result);
 599              $bot = false;
 600          }
 601  
 602          // Bot user, if they have a SID in the Request URI we need to get rid of it
 603          // otherwise they'll index this page with the SID, duplicate content oh my!
 604          if ($bot && isset($_GET['sid']))
 605          {
 606              send_status_line(301, 'Moved Permanently');
 607              redirect(build_url(array('sid')));
 608          }
 609  
 610          // If no data was returned one or more of the following occurred:
 611          // Key didn't match one in the DB
 612          // User does not exist
 613          // User is inactive
 614          // User is bot
 615          if (!is_array($this->data) || !count($this->data))
 616          {
 617              $this->cookie_data['k'] = '';
 618              $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS;
 619  
 620              if (!$bot)
 621              {
 622                  $sql = 'SELECT *
 623                      FROM ' . USERS_TABLE . '
 624                      WHERE user_id = ' . (int) $this->cookie_data['u'];
 625              }
 626              else
 627              {
 628                  // We give bots always the same session if it is not yet expired.
 629                  $sql = 'SELECT u.*, s.*
 630                      FROM ' . USERS_TABLE . ' u
 631                      LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id)
 632                      WHERE u.user_id = ' . (int) $bot;
 633              }
 634  
 635              $result = $db->sql_query($sql);
 636              $this->data = $db->sql_fetchrow($result);
 637              $db->sql_freeresult($result);
 638          }
 639  
 640          if ($this->data['user_id'] != ANONYMOUS && !$bot)
 641          {
 642              $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());
 643          }
 644          else
 645          {
 646              $this->data['session_last_visit'] = $this->time_now;
 647          }
 648  
 649          // Force user id to be integer...
 650          $this->data['user_id'] = (int) $this->data['user_id'];
 651  
 652          // At this stage we should have a filled data array, defined cookie u and k data.
 653          // data array should contain recent session info if we're a real user and a recent
 654          // session exists in which case session_id will also be set
 655  
 656          // Is user banned? Are they excluded? Won't return on ban, exists within method
 657          $this->check_ban_for_current_session($config);
 658  
 659          $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
 660          $this->data['is_bot'] = ($bot) ? true : false;
 661  
 662          // If our friend is a bot, we re-assign a previously assigned session
 663          if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id'])
 664          {
 665              // Only assign the current session if the ip, browser and forwarded_for match...
 666              if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
 667              {
 668                  $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
 669                  $u_ip = short_ipv6($this->ip, $config['ip_check']);
 670              }
 671              else
 672              {
 673                  $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
 674                  $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
 675              }
 676  
 677              $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
 678              $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
 679  
 680              $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
 681              $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
 682  
 683              if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for)
 684              {
 685                  $this->session_id = $this->data['session_id'];
 686  
 687                  // Only update session DB a minute or so after last update or if page changes
 688                  if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page']))
 689                  {
 690                      // Update the last visit time
 691                      $sql = 'UPDATE ' . USERS_TABLE . '
 692                          SET user_lastvisit = ' . (int) $this->data['session_time'] . '
 693                          WHERE user_id = ' . (int) $this->data['user_id'];
 694                      $db->sql_query($sql);
 695                  }
 696  
 697                  $SID = '?sid=';
 698                  $_SID = '';
 699                  return true;
 700              }
 701              else
 702              {
 703                  // If the ip and browser does not match make sure we only have one bot assigned to one session
 704                  $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']);
 705              }
 706          }
 707  
 708          $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false;
 709          $set_admin = ($set_admin && $this->data['is_registered']) ? true : false;
 710  
 711          // Create or update the session
 712          $sql_ary = array(
 713              'session_user_id'        => (int) $this->data['user_id'],
 714              'session_start'            => (int) $this->time_now,
 715              'session_last_visit'    => (int) $this->data['session_last_visit'],
 716              'session_time'            => (int) $this->time_now,
 717              'session_browser'        => (string) trim(substr($this->browser, 0, 149)),
 718              'session_forwarded_for'    => (string) $this->forwarded_for,
 719              'session_ip'            => (string) $this->ip,
 720              'session_autologin'        => ($session_autologin) ? 1 : 0,
 721              'session_admin'            => ($set_admin) ? 1 : 0,
 722              'session_viewonline'    => ($viewonline) ? 1 : 0,
 723          );
 724  
 725          if ($this->update_session_page)
 726          {
 727              $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
 728              $sql_ary['session_forum_id'] = $this->page['forum'];
 729          }
 730  
 731          $db->sql_return_on_error(true);
 732  
 733          $sql = 'DELETE
 734              FROM ' . SESSIONS_TABLE . '
 735              WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'
 736                  AND session_user_id = ' . ANONYMOUS;
 737  
 738          if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows()))
 739          {
 740              // Limit new sessions in 1 minute period (if required)
 741              if (empty($this->data['session_time']) && $config['active_sessions'])
 742              {
 743  //                $db->sql_return_on_error(false);
 744  
 745                  $sql = 'SELECT COUNT(session_id) AS sessions
 746                      FROM ' . SESSIONS_TABLE . '
 747                      WHERE session_time >= ' . ($this->time_now - 60);
 748                  $result = $db->sql_query($sql);
 749                  $row = $db->sql_fetchrow($result);
 750                  $db->sql_freeresult($result);
 751  
 752                  if ((int) $row['sessions'] > (int) $config['active_sessions'])
 753                  {
 754                      send_status_line(503, 'Service Unavailable');
 755                      trigger_error('BOARD_UNAVAILABLE');
 756                  }
 757              }
 758          }
 759  
 760          // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors.
 761          // Commented out because it will not allow forums to update correctly
 762  //        $db->sql_return_on_error(false);
 763  
 764          // Something quite important: session_page always holds the *last* page visited, except for the *first* visit.
 765          // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case.
 766          // 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.
 767          if (empty($this->data['session_id']))
 768          {
 769              // This is a temporary variable, only set for the very first visit
 770              $this->data['session_created'] = true;
 771          }
 772  
 773          $this->session_id = $this->data['session_id'] = md5(unique_id());
 774  
 775          $sql_ary['session_id'] = (string) $this->session_id;
 776          $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
 777          $sql_ary['session_forum_id'] = $this->page['forum'];
 778  
 779          $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
 780          $db->sql_query($sql);
 781  
 782          $db->sql_return_on_error(false);
 783  
 784          // Regenerate autologin/persistent login key
 785          if ($session_autologin)
 786          {
 787              $this->set_login_key();
 788          }
 789  
 790          // refresh data
 791          $SID = '?sid=' . $this->session_id;
 792          $_SID = $this->session_id;
 793          $this->data = array_merge($this->data, $sql_ary);
 794  
 795          if (!$bot)
 796          {
 797              $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000);
 798  
 799              $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire);
 800              $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire);
 801              $this->set_cookie('sid', $this->session_id, $cookie_expire);
 802  
 803              unset($cookie_expire);
 804  
 805              $sql = 'SELECT COUNT(session_id) AS sessions
 806                      FROM ' . SESSIONS_TABLE . '
 807                      WHERE session_user_id = ' . (int) $this->data['user_id'] . '
 808                      AND session_time >= ' . (int) ($this->time_now - (max((int) $config['session_length'], (int) $config['form_token_lifetime'])));
 809              $result = $db->sql_query($sql);
 810              $row = $db->sql_fetchrow($result);
 811              $db->sql_freeresult($result);
 812  
 813              if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt']))
 814              {
 815                  $this->data['user_form_salt'] = unique_id();
 816                  // Update the form key
 817                  $sql = 'UPDATE ' . USERS_TABLE . '
 818                      SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\'
 819                      WHERE user_id = ' . (int) $this->data['user_id'];
 820                  $db->sql_query($sql);
 821              }
 822          }
 823          else
 824          {
 825              $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now;
 826  
 827              // Update the last visit time
 828              $sql = 'UPDATE ' . USERS_TABLE . '
 829                  SET user_lastvisit = ' . (int) $this->data['session_time'] . '
 830                  WHERE user_id = ' . (int) $this->data['user_id'];
 831              $db->sql_query($sql);
 832  
 833              $SID = '?sid=';
 834              $_SID = '';
 835          }
 836  
 837          $session_data = $sql_ary;
 838          /**
 839          * Event to send new session data to extension
 840          * Read-only event
 841          *
 842          * @event core.session_create_after
 843          * @var    array        session_data                Associative array of session keys to be updated
 844          * @since 3.1.6-RC1
 845          */
 846          $vars = array('session_data');
 847          extract($phpbb_dispatcher->trigger_event('core.session_create_after', compact($vars)));
 848          unset($session_data);
 849  
 850          return true;
 851      }
 852  
 853      /**
 854      * Kills a session
 855      *
 856      * This method does what it says on the tin. It will delete a pre-existing session.
 857      * It resets cookie information (destroying any autologin key within that cookie data)
 858      * and update the users information from the relevant session data. It will then
 859      * grab guest user information.
 860      */
 861  	function session_kill($new_session = true)
 862      {
 863          global $SID, $_SID, $db, $phpbb_container, $phpbb_dispatcher;
 864  
 865          $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
 866              WHERE session_id = '" . $db->sql_escape($this->session_id) . "'
 867                  AND session_user_id = " . (int) $this->data['user_id'];
 868          $db->sql_query($sql);
 869  
 870          $user_id = (int) $this->data['user_id'];
 871          $session_id = $this->session_id;
 872          /**
 873          * Event to send session kill information to extension
 874          * Read-only event
 875          *
 876          * @event core.session_kill_after
 877          * @var    int        user_id                user_id of the session user.
 878          * @var    string        session_id                current user's session_id
 879          * @var    bool    new_session     should we create new session for user
 880          * @since 3.1.6-RC1
 881          */
 882          $vars = array('user_id', 'session_id', 'new_session');
 883          extract($phpbb_dispatcher->trigger_event('core.session_kill_after', compact($vars)));
 884          unset($user_id);
 885          unset($session_id);
 886  
 887          // Allow connecting logout with external auth method logout
 888          /* @var $provider_collection \phpbb\auth\provider_collection */
 889          $provider_collection = $phpbb_container->get('auth.provider_collection');
 890          $provider = $provider_collection->get_provider();
 891          $provider->logout($this->data, $new_session);
 892  
 893          if ($this->data['user_id'] != ANONYMOUS)
 894          {
 895              // Delete existing session, update last visit info first!
 896              if (!isset($this->data['session_time']))
 897              {
 898                  $this->data['session_time'] = time();
 899              }
 900  
 901              $sql = 'UPDATE ' . USERS_TABLE . '
 902                  SET user_lastvisit = ' . (int) $this->data['session_time'] . '
 903                  WHERE user_id = ' . (int) $this->data['user_id'];
 904              $db->sql_query($sql);
 905  
 906              if ($this->cookie_data['k'])
 907              {
 908                  $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
 909                      WHERE user_id = ' . (int) $this->data['user_id'] . "
 910                          AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
 911                  $db->sql_query($sql);
 912              }
 913  
 914              // Reset the data array
 915              $this->data = array();
 916  
 917              $sql = 'SELECT *
 918                  FROM ' . USERS_TABLE . '
 919                  WHERE user_id = ' . ANONYMOUS;
 920              $result = $db->sql_query($sql);
 921              $this->data = $db->sql_fetchrow($result);
 922              $db->sql_freeresult($result);
 923          }
 924  
 925          $cookie_expire = $this->time_now - 31536000;
 926          $this->set_cookie('u', '', $cookie_expire);
 927          $this->set_cookie('k', '', $cookie_expire);
 928          $this->set_cookie('sid', '', $cookie_expire);
 929          unset($cookie_expire);
 930  
 931          $SID = '?sid=';
 932          $this->session_id = $_SID = '';
 933  
 934          // To make sure a valid session is created we create one for the anonymous user
 935          if ($new_session)
 936          {
 937              $this->session_create(ANONYMOUS);
 938          }
 939  
 940          return true;
 941      }
 942  
 943      /**
 944      * Session garbage collection
 945      *
 946      * This looks a lot more complex than it really is. Effectively we are
 947      * deleting any sessions older than an admin definable limit. Due to the
 948      * way in which we maintain session data we have to ensure we update user
 949      * data before those sessions are destroyed. In addition this method
 950      * removes autologin key information that is older than an admin defined
 951      * limit.
 952      */
 953  	function session_gc()
 954      {
 955          global $db, $config, $phpbb_container, $phpbb_dispatcher;
 956  
 957          if (!$this->time_now)
 958          {
 959              $this->time_now = time();
 960          }
 961  
 962          /**
 963           * Get expired sessions for registered users, only most recent for each user
 964           * Inner SELECT gets most recent expired sessions for unique session_user_id
 965           * Outer SELECT gets data for them
 966           */
 967          $sql_select = 'SELECT s1.session_page, s1.session_user_id, s1.session_time AS recent_time
 968              FROM ' . SESSIONS_TABLE . ' AS s1
 969              INNER JOIN (
 970                  SELECT session_user_id, MAX(session_time) AS recent_time
 971                  FROM ' . SESSIONS_TABLE . '
 972                  WHERE session_time < ' . ($this->time_now - (int) $config['session_length']) . '
 973                      AND session_user_id <> ' . ANONYMOUS . '
 974                  GROUP BY session_user_id
 975              ) AS s2
 976              ON s1.session_user_id = s2.session_user_id
 977                  AND s1.session_time = s2.recent_time';
 978  
 979          switch ($db->get_sql_layer())
 980          {
 981              case 'sqlite3':
 982                  if (phpbb_version_compare($db->sql_server_info(true), '3.8.3', '>='))
 983                  {
 984                      // For SQLite versions 3.8.3+ which support Common Table Expressions (CTE)
 985                      $sql = "WITH s3 (session_page, session_user_id, session_time) AS ($sql_select)
 986                          UPDATE " . USERS_TABLE . '
 987                          SET (user_lastpage, user_lastvisit) = (SELECT session_page, session_time FROM s3 WHERE session_user_id = user_id)
 988                          WHERE EXISTS (SELECT session_user_id FROM s3 WHERE session_user_id = user_id)';
 989                      $db->sql_query($sql);
 990  
 991                      break;
 992                  }
 993  
 994              // No break, for SQLite versions prior to 3.8.3 and Oracle
 995              case 'oracle':
 996                  $result = $db->sql_query($sql_select);
 997                  while ($row = $db->sql_fetchrow($result))
 998                  {
 999                      $sql = 'UPDATE ' . USERS_TABLE . '
1000                          SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "'
1001                          WHERE user_id = " . (int) $row['session_user_id'];
1002                      $db->sql_query($sql);
1003                  }
1004                  $db->sql_freeresult($result);
1005              break;
1006  
1007              case 'mysqli':
1008                  $sql = 'UPDATE ' . USERS_TABLE . " u,
1009                      ($sql_select) s3
1010                      SET u.user_lastvisit = s3.recent_time, u.user_lastpage = s3.session_page
1011                      WHERE u.user_id = s3.session_user_id";
1012                  $db->sql_query($sql);
1013              break;
1014  
1015              default:
1016                  $sql = 'UPDATE ' . USERS_TABLE . "
1017                      SET user_lastvisit = s3.recent_time, user_lastpage = s3.session_page
1018                      FROM ($sql_select) s3
1019                      WHERE user_id = s3.session_user_id";
1020                  $db->sql_query($sql);
1021              break;
1022          }
1023  
1024          // Delete all expired sessions
1025          $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
1026              WHERE session_time < ' . ($this->time_now - (int) $config['session_length']);
1027          $db->sql_query($sql);
1028  
1029          // Update gc timer
1030          $config->set('session_last_gc', $this->time_now, false);
1031  
1032          if ($config['max_autologin_time'])
1033          {
1034              $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
1035                  WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time']));
1036              $db->sql_query($sql);
1037          }
1038  
1039          // only called from CRON; should be a safe workaround until the infrastructure gets going
1040          /* @var \phpbb\captcha\factory $captcha_factory */
1041          $captcha_factory = $phpbb_container->get('captcha.factory');
1042          $captcha_factory->garbage_collect($config['captcha_plugin']);
1043  
1044          $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . '
1045              WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']);
1046          $db->sql_query($sql);
1047  
1048          /**
1049          * Event to trigger extension on session_gc
1050          *
1051          * @event core.session_gc_after
1052          * @since 3.1.6-RC1
1053          */
1054          $phpbb_dispatcher->dispatch('core.session_gc_after');
1055  
1056          return;
1057      }
1058  
1059      /**
1060      * Sets a cookie
1061      *
1062      * 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.
1063      *
1064      * @param string $name        Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then.
1065      * @param string $cookiedata    The data to hold within the cookie
1066      * @param int $cookietime    The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set.
1067      * @param bool $httponly        Use HttpOnly. Defaults to true. Use false to make cookie accessible by client-side scripts.
1068      */
1069  	function set_cookie($name, $cookiedata, $cookietime, $httponly = true)
1070      {
1071          global $config, $phpbb_dispatcher;
1072  
1073          // If headers are already set, we just return
1074          if (headers_sent())
1075          {
1076              return;
1077          }
1078  
1079          $disable_cookie = false;
1080          /**
1081          * Event to modify or disable setting cookies
1082          *
1083          * @event core.set_cookie
1084          * @var    bool        disable_cookie    Set to true to disable setting this cookie
1085          * @var    string        name            Name of the cookie
1086          * @var    string        cookiedata        The data to hold within the cookie
1087          * @var    int            cookietime        The expiration time as UNIX timestamp
1088          * @var    bool        httponly        Use HttpOnly?
1089          * @since 3.2.9-RC1
1090          */
1091          $vars = array(
1092              'disable_cookie',
1093              'name',
1094              'cookiedata',
1095              'cookietime',
1096              'httponly',
1097          );
1098          extract($phpbb_dispatcher->trigger_event('core.set_cookie', compact($vars)));
1099  
1100          if ($disable_cookie)
1101          {
1102              return;
1103          }
1104  
1105          $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata);
1106          $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime);
1107          $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == '127.0.0.1' || strpos($config['cookie_domain'], '.') === false) ? '' : '; domain=' . $config['cookie_domain'];
1108  
1109          header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . ';' . (($httponly) ? ' HttpOnly' : ''), false);
1110      }
1111  
1112      /**
1113      * Check for banned user
1114      *
1115      * Checks whether the supplied user is banned by id, ip or email. If no parameters
1116      * are passed to the method pre-existing session data is used.
1117      *
1118      * @param int|false        $user_id        The user id
1119      * @param mixed            $user_ips        Can contain a string with one IP or an array of multiple IPs
1120      * @param string|false    $user_email        The user email
1121      * @param bool            $return            If $return is false this routine does not return on finding a banned user,
1122      *    it outputs a relevant message and stops execution.
1123      */
1124  	function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false)
1125      {
1126          global $config, $db, $phpbb_dispatcher;
1127  
1128          if (defined('IN_CHECK_BAN') || defined('SKIP_CHECK_BAN'))
1129          {
1130              return;
1131          }
1132  
1133          $banned = false;
1134          $cache_ttl = 3600;
1135          $where_sql = array();
1136  
1137          $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end
1138              FROM ' . BANLIST_TABLE . '
1139              WHERE ';
1140  
1141          // Determine which entries to check, only return those
1142          if ($user_email === false)
1143          {
1144              $where_sql[] = "ban_email = ''";
1145          }
1146  
1147          if ($user_ips === false)
1148          {
1149              $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)";
1150          }
1151  
1152          if ($user_id === false)
1153          {
1154              $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)';
1155          }
1156          else
1157          {
1158              $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0;
1159              $_sql = '(ban_userid = ' . $user_id;
1160  
1161              if ($user_email !== false)
1162              {
1163                  $_sql .= " OR ban_email <> ''";
1164              }
1165  
1166              if ($user_ips !== false)
1167              {
1168                  $_sql .= " OR ban_ip <> ''";
1169              }
1170  
1171              $_sql .= ')';
1172  
1173              $where_sql[] = $_sql;
1174          }
1175  
1176          $sql .= (count($where_sql)) ? implode(' AND ', $where_sql) : '';
1177          $result = $db->sql_query($sql, $cache_ttl);
1178  
1179          $ban_triggered_by = 'user';
1180          while ($row = $db->sql_fetchrow($result))
1181          {
1182              if ($row['ban_end'] && $row['ban_end'] < time())
1183              {
1184                  continue;
1185              }
1186  
1187              $ip_banned = false;
1188              if (!empty($row['ban_ip']))
1189              {
1190                  if (!is_array($user_ips))
1191                  {
1192                      $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips);
1193                  }
1194                  else
1195                  {
1196                      foreach ($user_ips as $user_ip)
1197                      {
1198                          if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip))
1199                          {
1200                              $ip_banned = true;
1201                              break;
1202                          }
1203                      }
1204                  }
1205              }
1206  
1207              if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) ||
1208                  $ip_banned ||
1209                  (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email)))
1210              {
1211                  if (!empty($row['ban_exclude']))
1212                  {
1213                      $banned = false;
1214                      break;
1215                  }
1216                  else
1217                  {
1218                      $banned = true;
1219                      $ban_row = $row;
1220  
1221                      if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id)
1222                      {
1223                          $ban_triggered_by = 'user';
1224                      }
1225                      else if ($ip_banned)
1226                      {
1227                          $ban_triggered_by = 'ip';
1228                      }
1229                      else
1230                      {
1231                          $ban_triggered_by = 'email';
1232                      }
1233  
1234                      // Don't break. Check if there is an exclude rule for this user
1235                  }
1236              }
1237          }
1238          $db->sql_freeresult($result);
1239  
1240          /**
1241          * Event to set custom ban type
1242          *
1243          * @event core.session_set_custom_ban
1244          * @var    bool        return                If $return is false this routine does not return on finding a banned user, it outputs a relevant message and stops execution
1245          * @var    bool        banned                Check if user already banned
1246          * @var    array|false    ban_row                Ban data
1247          * @var    string        ban_triggered_by    Method that caused ban, can be your custom method
1248          * @since 3.1.3-RC1
1249          */
1250          $ban_row = isset($ban_row) ? $ban_row : false;
1251          $vars = array('return', 'banned', 'ban_row', 'ban_triggered_by');
1252          extract($phpbb_dispatcher->trigger_event('core.session_set_custom_ban', compact($vars)));
1253  
1254          if ($banned && !$return)
1255          {
1256              global $phpbb_root_path, $phpEx;
1257  
1258              // If the session is empty we need to create a valid one...
1259              if (empty($this->session_id))
1260              {
1261                  // This seems to be no longer needed? - #14971
1262  //                $this->session_create(ANONYMOUS);
1263              }
1264  
1265              // Initiate environment ... since it won't be set at this stage
1266              $this->setup();
1267  
1268              // Logout the user, banned users are unable to use the normal 'logout' link
1269              if ($this->data['user_id'] != ANONYMOUS)
1270              {
1271                  $this->session_kill();
1272              }
1273  
1274              // We show a login box here to allow founders accessing the board if banned by IP
1275              if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS)
1276              {
1277                  $this->setup('ucp');
1278                  $this->data['is_registered'] = $this->data['is_bot'] = false;
1279  
1280                  // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again.
1281                  define('IN_CHECK_BAN', 1);
1282  
1283                  login_box("index.$phpEx");
1284  
1285                  // The false here is needed, else the user is able to circumvent the ban.
1286                  $this->session_kill(false);
1287              }
1288  
1289              // Ok, we catch the case of an empty session id for the anonymous user...
1290              // This can happen if the user is logging in, banned by username and the login_box() being called "again".
1291              if (empty($this->session_id) && defined('IN_CHECK_BAN'))
1292              {
1293                  $this->session_create(ANONYMOUS);
1294              }
1295  
1296              // Determine which message to output
1297              $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : '';
1298              $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM';
1299  
1300              $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx);
1301              $message = sprintf($this->lang[$message], $till_date, '<a href="' . $contact_link . '">', '</a>');
1302              $message .= ($ban_row['ban_give_reason']) ? '<br /><br />' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : '';
1303              $message .= '<br /><br /><em>' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . '</em>';
1304  
1305              // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page
1306              if (defined('IN_CRON'))
1307              {
1308                  garbage_collection();
1309                  exit_handler();
1310                  exit;
1311              }
1312  
1313              // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again
1314              $this->session_kill(false);
1315  
1316              trigger_error($message);
1317          }
1318  
1319          if (!empty($ban_row))
1320          {
1321              $ban_row['ban_triggered_by'] = $ban_triggered_by;
1322          }
1323  
1324          return ($banned && $ban_row) ? $ban_row : $banned;
1325      }
1326  
1327      /**
1328       * Check the current session for bans
1329       *
1330       * @return true if session user is banned.
1331       */
1332  	protected function check_ban_for_current_session($config)
1333      {
1334          if (!defined('SKIP_CHECK_BAN') && $this->data['user_type'] != USER_FOUNDER)
1335          {
1336              if (!$config['forwarded_for_check'])
1337              {
1338                  $this->check_ban($this->data['user_id'], $this->ip);
1339              }
1340              else
1341              {
1342                  $ips = explode(' ', $this->forwarded_for);
1343                  $ips[] = $this->ip;
1344                  $this->check_ban($this->data['user_id'], $ips);
1345              }
1346          }
1347      }
1348  
1349      /**
1350      * Check if ip is blacklisted by Spamhaus SBL
1351      *
1352      * Disables DNSBL setting if errors are returned by Spamhaus due to a policy violation.
1353      * https://www.spamhaus.com/product/help-for-spamhaus-public-mirror-users/
1354      *
1355      * @param string         $dnsbl    the blacklist to check against
1356      * @param string|false    $ip        the IPv4 address to check
1357      *
1358      * @return bool true if listed in spamhaus database, false if not
1359      */
1360  	function check_dnsbl_spamhaus($dnsbl, $ip = false)
1361      {
1362          global $config, $phpbb_log;
1363  
1364          if ($ip === false)
1365          {
1366              $ip = $this->ip;
1367          }
1368  
1369          // Spamhaus does not support IPv6 addresses.
1370          if (strpos($ip, ':') !== false)
1371          {
1372              return false;
1373          }
1374  
1375          if ($ip)
1376          {
1377              $quads = explode('.', $ip);
1378              $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0];
1379  
1380              $records = dns_get_record($reverse_ip . '.' . $dnsbl . '.', DNS_A);
1381              if (empty($records))
1382              {
1383                  return false;
1384              }
1385              else
1386              {
1387                  $error = false;
1388                  foreach ($records as $record)
1389                  {
1390                      if ($record['ip'] == '127.255.255.254')
1391                      {
1392                          $error = 'LOG_SPAMHAUS_OPEN_RESOLVER';
1393                          break;
1394                      }
1395                      else if ($record['ip'] == '127.255.255.255')
1396                      {
1397                          $error = 'LOG_SPAMHAUS_VOLUME_LIMIT';
1398                          break;
1399                      }
1400                  }
1401  
1402                  if ($error !== false)
1403                  {
1404                      $config->set('check_dnsbl', 0);
1405                      $phpbb_log->add('critical', $this->data['user_id'], $ip, $error);
1406                  }
1407                  else
1408                  {
1409                      // The existence of a non-error A record means it's a hit
1410                      return true;
1411                  }
1412              }
1413          }
1414  
1415          return false;
1416      }
1417  
1418      /**
1419      * Checks if an IPv4 address is in a specified DNS blacklist
1420      *
1421      * Only checks if a record is returned or not.
1422      *
1423      * @param string         $dnsbl    the blacklist to check against
1424      * @param string|false    $ip        the IPv4 address to check
1425      *
1426      * @return bool true if record is returned, false if not
1427      */
1428  	function check_dnsbl_ipv4_generic($dnsbl, $ip = false)
1429      {
1430          if ($ip === false)
1431          {
1432              $ip = $this->ip;
1433          }
1434  
1435          // This function does not support IPv6 addresses.
1436          if (strpos($ip, ':') !== false)
1437          {
1438              return false;
1439          }
1440  
1441          $quads = explode('.', $ip);
1442          $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0];
1443  
1444          if (checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true)
1445          {
1446              return true;
1447          }
1448  
1449          return false;
1450      }
1451  
1452      /**
1453      * Check if ip is blacklisted
1454      * This should be called only where absolutely necessary
1455      *
1456      * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups)
1457      *
1458      * @author satmd (from the php manual)
1459      * @param string         $mode    register/post - spamcop for example is omitted for posting
1460      * @param string|false    $ip        the IPv4 address to check
1461      *
1462      * @return false if ip is not blacklisted, else an array([checked server], [lookup])
1463      */
1464  	function check_dnsbl($mode, $ip = false)
1465      {
1466          if ($ip === false)
1467          {
1468              $ip = $this->ip;
1469          }
1470  
1471          // Neither Spamhaus nor Spamcop supports IPv6 addresses.
1472          if (strpos($ip, ':') !== false)
1473          {
1474              return false;
1475          }
1476  
1477          $dnsbl_check = array(
1478              'sbl.spamhaus.org'    => ['http://www.spamhaus.org/query/bl?ip=', 'check_dnsbl_spamhaus'],
1479          );
1480  
1481          if ($mode == 'register')
1482          {
1483              $dnsbl_check['bl.spamcop.net'] = ['http://spamcop.net/bl.shtml?', 'check_dnsbl_ipv4_generic'];
1484          }
1485  
1486          if ($ip)
1487          {
1488              // Need to be listed on all servers...
1489              $listed = true;
1490              $info = array();
1491  
1492              foreach ($dnsbl_check as $dnsbl => $lookup)
1493              {
1494                  if (call_user_func(array($this, $lookup[1]), $dnsbl, $ip) === true)
1495                  {
1496                      $info = array($dnsbl, $lookup[0] . $ip);
1497                  }
1498                  else
1499                  {
1500                      $listed = false;
1501                  }
1502              }
1503  
1504              if ($listed)
1505              {
1506                  return $info;
1507              }
1508          }
1509  
1510          return false;
1511      }
1512  
1513      /**
1514      * Check if URI is blacklisted
1515      * This should be called only where absolutely necessary, for example on the submitted website field
1516      * This function is not in use at the moment and is only included for testing purposes, it may not work at all!
1517      * This means it is untested at the moment and therefore commented out
1518      *
1519      * @param string $uri URI to check
1520      * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists
1521      function check_uribl($uri)
1522      {
1523          // Normally parse_url() is not intended to parse uris
1524          // We need to get the top-level domain name anyway... change.
1525          $uri = parse_url($uri);
1526  
1527          if ($uri === false || empty($uri['host']))
1528          {
1529              return false;
1530          }
1531  
1532          $uri = trim($uri['host']);
1533  
1534          if ($uri)
1535          {
1536              // One problem here... the return parameter for the "windows" method is different from what
1537              // we expect... this may render this check useless...
1538              if (checkdnsrr($uri . '.multi.uribl.com.', 'A') === true)
1539              {
1540                  return true;
1541              }
1542          }
1543  
1544          return false;
1545      }
1546      */
1547  
1548      /**
1549      * Set/Update a persistent login key
1550      *
1551      * This method creates or updates a persistent session key. When a user makes
1552      * use of persistent (formerly auto-) logins a key is generated and stored in the
1553      * DB. When they revisit with the same key it's automatically updated in both the
1554      * DB and cookie. Multiple keys may exist for each user representing different
1555      * browsers or locations. As with _any_ non-secure-socket no passphrase login this
1556      * remains vulnerable to exploit.
1557      */
1558  	function set_login_key($user_id = false, $key = false, $user_ip = false)
1559      {
1560          global $db, $phpbb_dispatcher;
1561  
1562          $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id;
1563          $user_ip = ($user_ip === false) ? $this->ip : $user_ip;
1564          $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key;
1565  
1566          $key_id = unique_id(hexdec(substr($this->session_id, 0, 8)));
1567  
1568          $sql_ary = array(
1569              'key_id'        => (string) md5($key_id),
1570              'last_ip'        => (string) $user_ip,
1571              'last_login'    => (int) time()
1572          );
1573  
1574          if (!$key)
1575          {
1576              $sql_ary += array(
1577                  'user_id'    => (int) $user_id
1578              );
1579          }
1580  
1581          if ($key)
1582          {
1583              $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . '
1584                  SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
1585                  WHERE user_id = ' . (int) $user_id . "
1586                      AND key_id = '" . $db->sql_escape(md5($key)) . "'";
1587          }
1588          else
1589          {
1590              $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
1591          }
1592  
1593          /**
1594           * Event to adjust autologin keys process
1595           *
1596           * @event core.set_login_key
1597           * @var    string|false    key            Current autologin key if exists, false otherwise
1598           * @var    string            key_id        New autologin key
1599           * @var    string            sql            SQL query to update/insert autologin key
1600           * @var    array            sql_ary        Aray with autologin key data
1601           * @var    int                user_id        Current user's ID
1602           * @var    string            user_ip        Current user's IP address
1603           * @since 3.3.2-RC1
1604           */
1605          $vars = [
1606              'key',
1607              'key_id',
1608              'sql',
1609              'sql_ary',
1610              'user_id',
1611              'user_ip',
1612          ];
1613          extract($phpbb_dispatcher->trigger_event('core.set_login_key', compact($vars)));
1614  
1615          $db->sql_query($sql);
1616  
1617          $this->cookie_data['k'] = $key_id;
1618  
1619          return false;
1620      }
1621  
1622      /**
1623      * Reset all login keys for the specified user
1624      *
1625      * This method removes all current login keys for a specified (or the current)
1626      * user. It will be called on password change to render old keys unusable
1627      */
1628  	function reset_login_keys($user_id = false)
1629      {
1630          global $db;
1631  
1632          $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id;
1633  
1634          $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
1635              WHERE user_id = ' . (int) $user_id;
1636          $db->sql_query($sql);
1637  
1638          // If the user is logged in, update last visit info first before deleting sessions
1639          $sql = 'SELECT session_time, session_page
1640              FROM ' . SESSIONS_TABLE . '
1641              WHERE session_user_id = ' . (int) $user_id . '
1642              ORDER BY session_time DESC';
1643          $result = $db->sql_query_limit($sql, 1);
1644          $row = $db->sql_fetchrow($result);
1645          $db->sql_freeresult($result);
1646  
1647          if ($row)
1648          {
1649              $sql = 'UPDATE ' . USERS_TABLE . '
1650                  SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "'
1651                  WHERE user_id = " . (int) $user_id;
1652              $db->sql_query($sql);
1653          }
1654  
1655          // Let's also clear any current sessions for the specified user_id
1656          // If it's the current user then we'll leave this session intact
1657          $sql_where = 'session_user_id = ' . (int) $user_id;
1658          $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : '';
1659  
1660          $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
1661              WHERE $sql_where";
1662          $db->sql_query($sql);
1663  
1664          // We're changing the password of the current user and they have a key
1665          // Lets regenerate it to be safe
1666          if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k'])
1667          {
1668              $this->set_login_key($user_id);
1669          }
1670      }
1671  
1672  
1673      /**
1674      * Check if the request originated from the same page.
1675      * @param bool $check_script_path If true, the path will be checked as well
1676      */
1677  	function validate_referer($check_script_path = false)
1678      {
1679          global $config, $request;
1680  
1681          // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason)
1682          if (empty($this->referer) || empty($this->host))
1683          {
1684              return true;
1685          }
1686  
1687          $host = htmlspecialchars($this->host, ENT_COMPAT);
1688          $ref = substr($this->referer, strpos($this->referer, '://') + 3);
1689  
1690          if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0)))
1691          {
1692              return false;
1693          }
1694          else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '')
1695          {
1696              $ref = substr($ref, strlen($host));
1697              $server_port = $request->server('SERVER_PORT', 0);
1698  
1699              if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0)
1700              {
1701                  $ref = substr($ref, strlen(":$server_port"));
1702              }
1703  
1704              if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0))
1705              {
1706                  return false;
1707              }
1708          }
1709  
1710          return true;
1711      }
1712  
1713  
1714  	function unset_admin()
1715      {
1716          global $db;
1717          $sql = 'UPDATE ' . SESSIONS_TABLE . '
1718              SET session_admin = 0
1719              WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'';
1720          $db->sql_query($sql);
1721      }
1722  
1723      /**
1724      * Update the session data
1725      *
1726      * @param array $session_data associative array of session keys to be updated
1727      * @param string $session_id optional session_id, defaults to current user's session_id
1728      */
1729  	public function update_session($session_data, $session_id = null)
1730      {
1731          global $db, $phpbb_dispatcher;
1732  
1733          $session_id = ($session_id) ? $session_id : $this->session_id;
1734  
1735          $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $session_data) . "
1736              WHERE session_id = '" . $db->sql_escape($session_id) . "'";
1737          $db->sql_query($sql);
1738  
1739          /**
1740          * Event to send update session information to extension
1741          * Read-only event
1742          *
1743          * @event core.update_session_after
1744          * @var    array        session_data                Associative array of session keys to be updated
1745          * @var    string        session_id                current user's session_id
1746          * @since 3.1.6-RC1
1747          */
1748          $vars = array('session_data', 'session_id');
1749          extract($phpbb_dispatcher->trigger_event('core.update_session_after', compact($vars)));
1750      }
1751  
1752  	public function update_session_infos()
1753      {
1754          global $config, $db, $request;
1755  
1756          // No need to update if it's a new session. Informations are already inserted by session_create()
1757          if (isset($this->data['session_created']) && $this->data['session_created'])
1758          {
1759              return;
1760          }
1761  
1762          // Do not update the session page for ajax requests, so the view online still works as intended
1763          $page_changed = $this->update_session_page && (!isset($this->data['session_page']) || $this->data['session_page'] != $this->page['page']) && !$request->is_ajax();
1764  
1765          // Only update session DB a minute or so after last update or if page changes
1766          if ($this->time_now - (isset($this->data['session_time']) ? $this->data['session_time'] : 0) > 60 || $page_changed)
1767          {
1768              $sql_ary = array('session_time' => $this->time_now);
1769  
1770              if ($page_changed)
1771              {
1772                  $sql_ary['session_page'] = substr($this->page['page'], 0, 199);
1773                  $sql_ary['session_forum_id'] = $this->page['forum'];
1774              }
1775  
1776              $db->sql_return_on_error(true);
1777  
1778              $this->update_session($sql_ary);
1779  
1780              $db->sql_return_on_error(false);
1781  
1782              $this->data = array_merge($this->data, $sql_ary);
1783  
1784              if ($this->data['user_id'] != ANONYMOUS && isset($config['new_member_post_limit']) && $this->data['user_new'] && $config['new_member_post_limit'] <= $this->data['user_posts'])
1785              {
1786                  $this->leave_newly_registered();
1787              }
1788          }
1789      }
1790  
1791      /**
1792       * Get user ID
1793       *
1794       * @return int User ID
1795       */
1796      public function id() : int
1797      {
1798          return isset($this->data['user_id']) ? (int) $this->data['user_id'] : ANONYMOUS;
1799      }
1800  }


Generated: Wed Feb 22 20:16:20 2023 Cross-referenced by PHPXref 0.7.1