Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

session.php

Zuletzt modifiziert: 02.04.2025, 15:01 - Dateigröße: 57.18 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  namespace phpbb;
0015   
0016  /**
0017  * Session class
0018  */
0019  class session
0020  {
0021      var $cookie_data = array();
0022      var $page = array();
0023      var $data = array();
0024      var $browser = '';
0025      var $forwarded_for = '';
0026      var $host = '';
0027      var $session_id = '';
0028      var $ip = '';
0029      var $load = 0;
0030      var $time_now = 0;
0031      var $update_session_page = true;
0032   
0033      /**
0034       * Extract current session page
0035       *
0036       * @param string $root_path current root path (phpbb_root_path)
0037       * @return array
0038       */
0039      static function extract_current_page($root_path)
0040      {
0041          global $request, $symfony_request, $phpbb_filesystem;
0042   
0043          $page_array = array();
0044   
0045          // First of all, get the request uri...
0046          $script_name = $request->escape($symfony_request->getScriptName(), true);
0047          $args = $request->escape(explode('&', $symfony_request->getQueryString()), true);
0048   
0049          // 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...
0050          if (!$script_name)
0051          {
0052              $script_name = html_entity_decode($request->server('REQUEST_URI'), ENT_COMPAT);
0053              $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name;
0054              $page_array['failover'] = 1;
0055          }
0056   
0057          // Replace backslashes and doubled slashes (could happen on some proxy setups)
0058          $script_name = str_replace(array('\\', '//'), '/', $script_name);
0059   
0060          // Now, remove the sid and let us get a clean query string...
0061          $use_args = array();
0062   
0063          // Since some browser do not encode correctly we need to do this with some "special" characters...
0064          // " -> %22, ' => %27, < -> %3C, > -> %3E
0065          $find = array('"', "'", '<', '>', '&quot;', '&lt;', '&gt;');
0066          $replace = array('%22', '%27', '%3C', '%3E', '%22', '%3C', '%3E');
0067   
0068          foreach ($args as $key => $argument)
0069          {
0070              if (strpos($argument, 'sid=') === 0)
0071              {
0072                  continue;
0073              }
0074   
0075              $use_args[] = str_replace($find, $replace, $argument);
0076          }
0077          unset($args);
0078   
0079          // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2
0080   
0081          // The current query string
0082          $query_string = trim(implode('&', $use_args));
0083   
0084          // basenamed page name (for example: index.php)
0085          $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name);
0086          $page_name = urlencode(htmlspecialchars($page_name, ENT_COMPAT));
0087   
0088          $symfony_request_path = $phpbb_filesystem->clean_path($symfony_request->getPathInfo());
0089          if ($symfony_request_path !== '/')
0090          {
0091              $page_name .= str_replace('%2F', '/', urlencode($symfony_request_path));
0092          }
0093   
0094          if (substr($root_path, 0, 2) === './' && strpos($root_path, '..') === false)
0095          {
0096              $root_dirs = explode('/', str_replace('\\', '/', rtrim($root_path, '/')));
0097              $page_dirs = explode('/', str_replace('\\', '/', '.'));
0098          }
0099          else
0100          {
0101              // current directory within the phpBB root (for example: adm)
0102              $root_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath($root_path)));
0103              $page_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath('./')));
0104          }
0105   
0106          $intersection = array_intersect_assoc($root_dirs, $page_dirs);
0107   
0108          $root_dirs = array_diff_assoc($root_dirs, $intersection);
0109          $page_dirs = array_diff_assoc($page_dirs, $intersection);
0110   
0111          $page_dir = str_repeat('../', count($root_dirs)) . implode('/', $page_dirs);
0112   
0113          if ($page_dir && substr($page_dir, -1, 1) == '/')
0114          {
0115              $page_dir = substr($page_dir, 0, -1);
0116          }
0117   
0118          // Current page from phpBB root (for example: adm/index.php?i=10&b=2)
0119          $page = (($page_dir) ? $page_dir . '/' : '') . $page_name;
0120          if ($query_string)
0121          {
0122              $page .= '?' . $query_string;
0123          }
0124   
0125          // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in /
0126          $script_path = $symfony_request->getBasePath();
0127   
0128          // The script path from the webroot to the phpBB root (for example: /phpBB3/)
0129          $script_dirs = explode('/', $script_path);
0130          array_splice($script_dirs, -count($page_dirs));
0131          $root_script_path = implode('/', $script_dirs) . (count($root_dirs) ? '/' . implode('/', $root_dirs) : '');
0132   
0133          // We are on the base level (phpBB root == webroot), lets adjust the variables a bit...
0134          if (!$root_script_path)
0135          {
0136              $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path;
0137          }
0138   
0139          $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/';
0140          $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/';
0141   
0142          $forum_id = $request->variable('f', 0);
0143          // maximum forum id value is maximum value of mediumint unsigned column
0144          $forum_id = ($forum_id > 0 && $forum_id < 16777215) ? $forum_id : 0;
0145   
0146          $page_array += array(
0147              'page_name'            => $page_name,
0148              'page_dir'            => $page_dir,
0149   
0150              'query_string'        => $query_string,
0151              'script_path'        => str_replace(' ', '%20', htmlspecialchars($script_path, ENT_COMPAT)),
0152              'root_script_path'    => str_replace(' ', '%20', htmlspecialchars($root_script_path, ENT_COMPAT)),
0153   
0154              'page'                => $page,
0155              'forum'                => $forum_id,
0156          );
0157   
0158          return $page_array;
0159      }
0160   
0161      /**
0162      * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present.
0163      */
0164      function extract_current_hostname()
0165      {
0166          global $config, $request;
0167   
0168          // Get hostname
0169          $host = html_entity_decode($request->header('Host', $request->server('SERVER_NAME')), ENT_COMPAT);
0170   
0171          // Should be a string and lowered
0172          $host = (string) strtolower($host);
0173   
0174          // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid
0175          if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name']))
0176          {
0177              return $host;
0178          }
0179   
0180          // Is the host actually a IP? If so, we use the IP... (IPv4)
0181          if (long2ip(ip2long($host)) === $host)
0182          {
0183              return $host;
0184          }
0185   
0186          // 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
0187          $host = @parse_url('http://' . $host);
0188          $host = (!empty($host['host'])) ? $host['host'] : '';
0189   
0190          // Remove any portions not removed by parse_url (#)
0191          $host = str_replace('#', '', $host);
0192   
0193          // If, by any means, the host is now empty, we will use a "best approach" way to guess one
0194          if (empty($host))
0195          {
0196              if (!empty($config['server_name']))
0197              {
0198                  $host = $config['server_name'];
0199              }
0200              else if (!empty($config['cookie_domain']))
0201              {
0202                  $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain'];
0203              }
0204              else
0205              {
0206                  // Set to OS hostname or localhost
0207                  $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost';
0208              }
0209          }
0210   
0211          // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set)
0212          return $host;
0213      }
0214   
0215      /**
0216      * Start session management
0217      *
0218      * This is where all session activity begins. We gather various pieces of
0219      * information from the client and server. We test to see if a session already
0220      * exists. If it does, fine and dandy. If it doesn't we'll go on to create a
0221      * new one ... pretty logical heh? We also examine the system load (if we're
0222      * running on a system which makes such information readily available) and
0223      * halt if it's above an admin definable limit.
0224      *
0225      * @param bool $update_session_page if true the session page gets updated.
0226      *            This can be set to circumvent certain scripts to update the users last visited page.
0227      */
0228      function session_begin($update_session_page = true)
0229      {
0230          global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path;
0231          global $request, $phpbb_container, $user, $phpbb_log, $phpbb_dispatcher;
0232   
0233          // Give us some basic information
0234          $this->time_now                = time();
0235          $this->cookie_data            = array('u' => 0, 'k' => '');
0236          $this->update_session_page    = $update_session_page;
0237          $this->browser                = $request->header('User-Agent');
0238          $this->referer                = $request->header('Referer');
0239          $this->forwarded_for        = $request->header('X-Forwarded-For');
0240   
0241          $this->host                    = $this->extract_current_hostname();
0242          $this->page                    = $this->extract_current_page($phpbb_root_path);
0243   
0244          // if the forwarded for header shall be checked we have to validate its contents
0245          if ($config['forwarded_for_check'])
0246          {
0247              $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for));
0248   
0249              // split the list of IPs
0250              $ips = explode(' ', $this->forwarded_for);
0251              foreach ($ips as $ip)
0252              {
0253                  if (!filter_var($ip, FILTER_VALIDATE_IP))
0254                  {
0255                      // contains invalid data, don't use the forwarded for header
0256                      $this->forwarded_for = '';
0257                      break;
0258                  }
0259              }
0260          }
0261          else
0262          {
0263              $this->forwarded_for = '';
0264          }
0265   
0266          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))
0267          {
0268              $this->cookie_data['u'] = $request->variable($config['cookie_name'] . '_u', 0, false, \phpbb\request\request_interface::COOKIE);
0269              $this->cookie_data['k'] = $request->variable($config['cookie_name'] . '_k', '', false, \phpbb\request\request_interface::COOKIE);
0270              $this->session_id         = $request->variable($config['cookie_name'] . '_sid', '', false, \phpbb\request\request_interface::COOKIE);
0271   
0272              $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid=';
0273              $_SID = (defined('NEED_SID')) ? $this->session_id : '';
0274   
0275              if (empty($this->session_id))
0276              {
0277                  $this->session_id = $_SID = $request->variable('sid', '');
0278                  $SID = '?sid=' . $this->session_id;
0279                  $this->cookie_data = array('u' => 0, 'k' => '');
0280              }
0281          }
0282          else
0283          {
0284              $this->session_id = $_SID = $request->variable('sid', '');
0285              $SID = '?sid=' . $this->session_id;
0286          }
0287   
0288          $_EXTRA_URL = array();
0289   
0290          // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests
0291          // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip.
0292          $ip = html_entity_decode($request->server('REMOTE_ADDR'), ENT_COMPAT);
0293          $ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $ip));
0294   
0295          /**
0296          * Event to alter user IP address
0297          *
0298          * @event core.session_ip_after
0299          * @var    string    ip    REMOTE_ADDR
0300          * @since 3.1.10-RC1
0301          */
0302          $vars = array('ip');
0303          extract($phpbb_dispatcher->trigger_event('core.session_ip_after', compact($vars)));
0304   
0305          // split the list of IPs
0306          $ips = explode(' ', trim($ip));
0307   
0308          // Default IP if REMOTE_ADDR is invalid
0309          $this->ip = '127.0.0.1';
0310   
0311          foreach ($ips as $ip)
0312          {
0313              // Normalise IP address
0314              $ip = phpbb_ip_normalise($ip);
0315   
0316              if ($ip === false)
0317              {
0318                  // IP address is invalid.
0319                  break;
0320              }
0321   
0322              // IP address is valid.
0323              $this->ip = $ip;
0324          }
0325   
0326          $this->load = false;
0327   
0328          // Load limit check (if applicable)
0329          if ($config['limit_load'] || $config['limit_search_load'])
0330          {
0331              if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg'))))
0332              {
0333                  $this->load = array_slice($load, 0, 1);
0334                  $this->load = floatval($this->load[0]);
0335              }
0336              else
0337              {
0338                  $config->set('limit_load', '0');
0339                  $config->set('limit_search_load', '0');
0340              }
0341          }
0342   
0343          // if no session id is set, redirect to index.php
0344          $session_id = $request->variable('sid', '');
0345          if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id))
0346          {
0347              send_status_line(401, 'Unauthorized');
0348              redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
0349          }
0350   
0351          // if session id is set
0352          if (!empty($this->session_id))
0353          {
0354              $sql = 'SELECT u.*, s.*
0355                  FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u
0356                  WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "'
0357                      AND u.user_id = s.session_user_id";
0358              $result = $db->sql_query($sql);
0359              $this->data = $db->sql_fetchrow($result);
0360              $db->sql_freeresult($result);
0361   
0362              // Did the session exist in the DB?
0363              if (isset($this->data['user_id']))
0364              {
0365                  // Validate IP length according to admin ... enforces an IP
0366                  // check on bots if admin requires this
0367  //                $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check'];
0368   
0369                  if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
0370                  {
0371                      $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
0372                      $u_ip = short_ipv6($this->ip, $config['ip_check']);
0373                  }
0374                  else
0375                  {
0376                      $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
0377                      $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
0378                  }
0379   
0380                  $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
0381                  $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
0382   
0383                  $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
0384                  $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
0385   
0386                  // referer checks
0387                  // The @ before $config['referer_validation'] suppresses notices present while running the updater
0388                  $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH);
0389                  $referer_valid = true;
0390   
0391                  // we assume HEAD and TRACE to be foul play and thus only whitelist GET
0392                  if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get')
0393                  {
0394                      $referer_valid = $this->validate_referer($check_referer_path);
0395                  }
0396   
0397                  if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid)
0398                  {
0399                      $session_expired = false;
0400   
0401                      // Check whether the session is still valid if we have one
0402                      /* @var $provider_collection \phpbb\auth\provider_collection */
0403                      $provider_collection = $phpbb_container->get('auth.provider_collection');
0404                      $provider = $provider_collection->get_provider();
0405   
0406                      if (!($provider instanceof \phpbb\auth\provider\provider_interface))
0407                      {
0408                          throw new \RuntimeException($provider . ' must implement \phpbb\auth\provider\provider_interface');
0409                      }
0410   
0411                      $ret = $provider->validate_session($this->data);
0412                      if ($ret !== null && !$ret)
0413                      {
0414                          $session_expired = true;
0415                      }
0416   
0417                      if (!$session_expired)
0418                      {
0419                          // Check the session length timeframe if autologin is not enabled.
0420                          // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide.
0421                          if (!$this->data['session_autologin'])
0422                          {
0423                              if ($this->data['session_time'] < $this->time_now - ((int) $config['session_length'] + 60))
0424                              {
0425                                  $session_expired = true;
0426                              }
0427                          }
0428                          else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60))
0429                          {
0430                              $session_expired = true;
0431                          }
0432                      }
0433   
0434                      if (!$session_expired)
0435                      {
0436                          $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
0437                          $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false;
0438                          $this->data['user_lang'] = basename($this->data['user_lang']);
0439   
0440                          // Is user banned? Are they excluded? Won't return on ban, exists within method
0441                          $this->check_ban_for_current_session($config);
0442   
0443                          // Update user last active time accordingly, but in a minute or so
0444                          if ($this->time_now - (int) $this->data['user_last_active'] > 60)
0445                          {
0446                              $this->update_last_active_time();
0447                          }
0448   
0449                          return true;
0450                      }
0451                  }
0452                  else
0453                  {
0454                      // Added logging temporarily to help debug bugs...
0455                      if ($phpbb_container->getParameter('session.log_errors') && $this->data['user_id'] != ANONYMOUS)
0456                      {
0457                          if ($referer_valid)
0458                          {
0459                              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IP_BROWSER_FORWARDED_CHECK', false, array(
0460                                  $u_ip,
0461                                  $s_ip,
0462                                  $u_browser,
0463                                  $s_browser,
0464                                  htmlspecialchars($u_forwarded_for, ENT_COMPAT),
0465                                  htmlspecialchars($s_forwarded_for, ENT_COMPAT)
0466                              ));
0467                          }
0468                          else
0469                          {
0470                              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_REFERER_INVALID', false, array($this->referer));
0471                          }
0472                      }
0473                  }
0474              }
0475          }
0476   
0477          // If we reach here then no (valid) session exists. So we'll create a new one
0478          return $this->session_create();
0479      }
0480   
0481      /**
0482      * Create a new session
0483      *
0484      * If upon trying to start a session we discover there is nothing existing we
0485      * jump here. Additionally this method is called directly during login to regenerate
0486      * the session for the specific user. In this method we carry out a number of tasks;
0487      * garbage collection, (search)bot checking, banned user comparison. Basically
0488      * though this method will result in a new session for a specific user.
0489      */
0490      function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true)
0491      {
0492          global $SID, $_SID, $db, $config, $cache, $phpbb_container, $phpbb_dispatcher;
0493   
0494          $this->data = array();
0495   
0496          /* Garbage collection ... remove old sessions updating user information
0497          // if necessary. It means (potentially) 11 queries but only infrequently
0498          if ($this->time_now > $config['session_last_gc'] + $config['session_gc'])
0499          {
0500              $this->session_gc();
0501          }*/
0502   
0503          // Do we allow autologin on this board? No? Then override anything
0504          // that may be requested here
0505          if (!$config['allow_autologin'])
0506          {
0507              $this->cookie_data['k'] = $persist_login = false;
0508          }
0509   
0510          /**
0511          * Here we do a bot check, oh er saucy! No, not that kind of bot
0512          * check. We loop through the list of bots defined by the admin and
0513          * see if we have any useragent and/or IP matches. If we do, this is a
0514          * bot, act accordingly
0515          */
0516          $bot = false;
0517          $active_bots = $cache->obtain_bots();
0518   
0519          foreach ($active_bots as $row)
0520          {
0521              if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser))
0522              {
0523                  $bot = $row['user_id'];
0524              }
0525   
0526              // If ip is supplied, we will make sure the ip is matching too...
0527              if ($row['bot_ip'] && ($bot || !$row['bot_agent']))
0528              {
0529                  // Set bot to false, then we only have to set it to true if it is matching
0530                  $bot = false;
0531   
0532                  foreach (explode(',', $row['bot_ip']) as $bot_ip)
0533                  {
0534                      $bot_ip = trim($bot_ip);
0535   
0536                      if (!$bot_ip)
0537                      {
0538                          continue;
0539                      }
0540   
0541                      if (strpos($this->ip, $bot_ip) === 0)
0542                      {
0543                          $bot = (int) $row['user_id'];
0544                          break;
0545                      }
0546                  }
0547              }
0548   
0549              if ($bot)
0550              {
0551                  break;
0552              }
0553          }
0554   
0555          /* @var $provider_collection \phpbb\auth\provider_collection */
0556          $provider_collection = $phpbb_container->get('auth.provider_collection');
0557          $provider = $provider_collection->get_provider();
0558          $this->data = $provider->autologin();
0559   
0560          if ($user_id !== false && isset($this->data['user_id']) && $this->data['user_id'] != $user_id)
0561          {
0562              $this->data = array();
0563          }
0564   
0565          if (isset($this->data['user_id']))
0566          {
0567              $this->cookie_data['k'] = '';
0568              $this->cookie_data['u'] = $this->data['user_id'];
0569          }
0570   
0571          // If we're presented with an autologin key we'll join against it.
0572          // Else if we've been passed a user_id we'll grab data based on that
0573          if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && empty($this->data))
0574          {
0575              $sql = 'SELECT u.*
0576                  FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k
0577                  WHERE u.user_id = ' . (int) $this->cookie_data['u'] . '
0578                      AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ")
0579                      AND k.user_id = u.user_id
0580                      AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
0581              $result = $db->sql_query($sql);
0582              $user_data = $db->sql_fetchrow($result);
0583   
0584              if ($user_id === false || (isset($user_data['user_id']) && $user_id == $user_data['user_id']))
0585              {
0586                  $this->data = $user_data;
0587                  $bot = false;
0588              }
0589   
0590              $db->sql_freeresult($result);
0591          }
0592   
0593          if ($user_id !== false && empty($this->data))
0594          {
0595              $this->cookie_data['k'] = '';
0596              $this->cookie_data['u'] = $user_id;
0597   
0598              $sql = 'SELECT *
0599                  FROM ' . USERS_TABLE . '
0600                  WHERE user_id = ' . (int) $this->cookie_data['u'] . '
0601                      AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')';
0602              $result = $db->sql_query($sql);
0603              $this->data = $db->sql_fetchrow($result);
0604              $db->sql_freeresult($result);
0605              $bot = false;
0606          }
0607   
0608          // Bot user, if they have a SID in the Request URI we need to get rid of it
0609          // otherwise they'll index this page with the SID, duplicate content oh my!
0610          if ($bot && isset($_GET['sid']))
0611          {
0612              send_status_line(301, 'Moved Permanently');
0613              redirect(build_url(array('sid')));
0614          }
0615   
0616          // If no data was returned one or more of the following occurred:
0617          // Key didn't match one in the DB
0618          // User does not exist
0619          // User is inactive
0620          // User is bot
0621          if (!is_array($this->data) || !count($this->data))
0622          {
0623              $this->cookie_data['k'] = '';
0624              $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS;
0625   
0626              if (!$bot)
0627              {
0628                  $sql = 'SELECT *
0629                      FROM ' . USERS_TABLE . '
0630                      WHERE user_id = ' . (int) $this->cookie_data['u'];
0631              }
0632              else
0633              {
0634                  // We give bots always the same session if it is not yet expired.
0635                  $sql = 'SELECT u.*, s.*
0636                      FROM ' . USERS_TABLE . ' u
0637                      LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id)
0638                      WHERE u.user_id = ' . (int) $bot;
0639              }
0640   
0641              $result = $db->sql_query($sql);
0642              $this->data = $db->sql_fetchrow($result);
0643              $db->sql_freeresult($result);
0644          }
0645   
0646          if ($this->data['user_id'] != ANONYMOUS && !$bot)
0647          {
0648              $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());
0649          }
0650          else
0651          {
0652              $this->data['session_last_visit'] = $this->time_now;
0653          }
0654   
0655          // Force user id to be integer...
0656          $this->data['user_id'] = (int) $this->data['user_id'];
0657   
0658          // At this stage we should have a filled data array, defined cookie u and k data.
0659          // data array should contain recent session info if we're a real user and a recent
0660          // session exists in which case session_id will also be set
0661   
0662          // Is user banned? Are they excluded? Won't return on ban, exists within method
0663          $this->check_ban_for_current_session($config);
0664   
0665          $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
0666          $this->data['is_bot'] = ($bot) ? true : false;
0667   
0668          // If our friend is a bot, we re-assign a previously assigned session
0669          if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id'])
0670          {
0671              // Only assign the current session if the ip, browser and forwarded_for match...
0672              if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
0673              {
0674                  $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
0675                  $u_ip = short_ipv6($this->ip, $config['ip_check']);
0676              }
0677              else
0678              {
0679                  $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
0680                  $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
0681              }
0682   
0683              $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
0684              $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
0685   
0686              $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
0687              $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
0688   
0689              if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for)
0690              {
0691                  $this->session_id = $this->data['session_id'];
0692   
0693                  // Only update session DB a minute or so after last update or if page changes
0694                  if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page']))
0695                  {
0696                      // Update the last visit time
0697                      $this->update_user_lastvisit();
0698                  }
0699   
0700                  $SID = '?sid=';
0701                  $_SID = '';
0702                  return true;
0703              }
0704              else
0705              {
0706                  // If the ip and browser does not match make sure we only have one bot assigned to one session
0707                  $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']);
0708              }
0709          }
0710   
0711          $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false;
0712          $set_admin = ($set_admin && $this->data['is_registered']) ? true : false;
0713   
0714          // Create or update the session
0715          $sql_ary = array(
0716              'session_user_id'        => (int) $this->data['user_id'],
0717              'session_start'            => (int) $this->time_now,
0718              'session_last_visit'    => (int) $this->data['session_last_visit'],
0719              'session_time'            => (int) $this->time_now,
0720              'session_browser'        => (string) trim(substr($this->browser, 0, 149)),
0721              'session_forwarded_for'    => (string) $this->forwarded_for,
0722              'session_ip'            => (string) $this->ip,
0723              'session_autologin'        => ($session_autologin) ? 1 : 0,
0724              'session_admin'            => ($set_admin) ? 1 : 0,
0725              'session_viewonline'    => ($viewonline) ? 1 : 0,
0726          );
0727   
0728          if ($this->update_session_page)
0729          {
0730              $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
0731              $sql_ary['session_forum_id'] = $this->page['forum'];
0732          }
0733   
0734          $db->sql_return_on_error(true);
0735   
0736          $sql = 'DELETE
0737              FROM ' . SESSIONS_TABLE . '
0738              WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'
0739                  AND session_user_id = ' . ANONYMOUS;
0740   
0741          if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows()))
0742          {
0743              // Limit new sessions in 1 minute period (if required)
0744              if (empty($this->data['session_time']) && $config['active_sessions'])
0745              {
0746  //                $db->sql_return_on_error(false);
0747   
0748                  $sql = 'SELECT COUNT(session_id) AS sessions
0749                      FROM ' . SESSIONS_TABLE . '
0750                      WHERE session_time >= ' . ($this->time_now - 60);
0751                  $result = $db->sql_query($sql);
0752                  $row = $db->sql_fetchrow($result);
0753                  $db->sql_freeresult($result);
0754   
0755                  if ((int) $row['sessions'] > (int) $config['active_sessions'])
0756                  {
0757                      send_status_line(503, 'Service Unavailable');
0758                      trigger_error('BOARD_UNAVAILABLE');
0759                  }
0760              }
0761          }
0762   
0763          // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors.
0764          // Commented out because it will not allow forums to update correctly
0765  //        $db->sql_return_on_error(false);
0766   
0767          // Something quite important: session_page always holds the *last* page visited, except for the *first* visit.
0768          // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case.
0769          // 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.
0770          if (empty($this->data['session_id']))
0771          {
0772              // This is a temporary variable, only set for the very first visit
0773              $this->data['session_created'] = true;
0774          }
0775   
0776          $this->session_id = $this->data['session_id'] = md5(unique_id());
0777   
0778          $sql_ary['session_id'] = (string) $this->session_id;
0779          $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
0780          $sql_ary['session_forum_id'] = $this->page['forum'];
0781   
0782          $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
0783          $db->sql_query($sql);
0784   
0785          $db->sql_return_on_error(false);
0786   
0787          // Regenerate autologin/persistent login key
0788          if ($session_autologin)
0789          {
0790              $this->set_login_key();
0791          }
0792   
0793          // refresh data
0794          $SID = '?sid=' . $this->session_id;
0795          $_SID = $this->session_id;
0796          $this->data = array_merge($this->data, $sql_ary);
0797   
0798          if (!$bot)
0799          {
0800              $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000);
0801   
0802              $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire);
0803              $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire);
0804              $this->set_cookie('sid', $this->session_id, $cookie_expire);
0805   
0806              unset($cookie_expire);
0807   
0808              $sql = 'SELECT COUNT(session_id) AS sessions
0809                      FROM ' . SESSIONS_TABLE . '
0810                      WHERE session_user_id = ' . (int) $this->data['user_id'] . '
0811                      AND session_time >= ' . (int) ($this->time_now - (max((int) $config['session_length'], (int) $config['form_token_lifetime'])));
0812              $result = $db->sql_query($sql);
0813              $row = $db->sql_fetchrow($result);
0814              $db->sql_freeresult($result);
0815   
0816              if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt']))
0817              {
0818                  $this->data['user_form_salt'] = unique_id();
0819                  // Update the form key
0820                  $sql = 'UPDATE ' . USERS_TABLE . '
0821                      SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\',
0822                          user_last_active = ' . (int) $this->time_now . '
0823                      WHERE user_id = ' . (int) $this->data['user_id'];
0824                  $db->sql_query($sql);
0825              }
0826              else
0827              {
0828                  $this->update_last_active_time();
0829              }
0830          }
0831          else
0832          {
0833              $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now;
0834   
0835              $this->update_user_lastvisit();
0836   
0837              $SID = '?sid=';
0838              $_SID = '';
0839          }
0840   
0841          $session_data = $sql_ary;
0842          /**
0843          * Event to send new session data to extension
0844          * Read-only event
0845          *
0846          * @event core.session_create_after
0847          * @var    array        session_data                Associative array of session keys to be updated
0848          * @since 3.1.6-RC1
0849          */
0850          $vars = array('session_data');
0851          extract($phpbb_dispatcher->trigger_event('core.session_create_after', compact($vars)));
0852          unset($session_data);
0853   
0854          return true;
0855      }
0856   
0857      /**
0858      * Kills a session
0859      *
0860      * This method does what it says on the tin. It will delete a pre-existing session.
0861      * It resets cookie information (destroying any autologin key within that cookie data)
0862      * and update the users information from the relevant session data. It will then
0863      * grab guest user information.
0864      */
0865      function session_kill($new_session = true)
0866      {
0867          global $SID, $_SID, $db, $phpbb_container, $phpbb_dispatcher;
0868   
0869          $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
0870              WHERE session_id = '" . $db->sql_escape($this->session_id) . "'
0871                  AND session_user_id = " . (int) $this->data['user_id'];
0872          $db->sql_query($sql);
0873   
0874          $user_id = (int) $this->data['user_id'];
0875          $session_id = $this->session_id;
0876          /**
0877          * Event to send session kill information to extension
0878          * Read-only event
0879          *
0880          * @event core.session_kill_after
0881          * @var    int        user_id                user_id of the session user.
0882          * @var    string        session_id                current user's session_id
0883          * @var    bool    new_session     should we create new session for user
0884          * @since 3.1.6-RC1
0885          */
0886          $vars = array('user_id', 'session_id', 'new_session');
0887          extract($phpbb_dispatcher->trigger_event('core.session_kill_after', compact($vars)));
0888          unset($user_id);
0889          unset($session_id);
0890   
0891          // Allow connecting logout with external auth method logout
0892          /* @var $provider_collection \phpbb\auth\provider_collection */
0893          $provider_collection = $phpbb_container->get('auth.provider_collection');
0894          $provider = $provider_collection->get_provider();
0895          $provider->logout($this->data, $new_session);
0896   
0897          if ($this->data['user_id'] != ANONYMOUS)
0898          {
0899              // Delete existing session, update last visit info first!
0900              if (!isset($this->data['session_time']))
0901              {
0902                  $this->data['session_time'] = time();
0903              }
0904   
0905              $sql = 'UPDATE ' . USERS_TABLE . '
0906                  SET user_lastvisit = ' . (int) $this->data['session_time'] . '
0907                  WHERE user_id = ' . (int) $this->data['user_id'];
0908              $db->sql_query($sql);
0909   
0910              if ($this->cookie_data['k'])
0911              {
0912                  $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
0913                      WHERE user_id = ' . (int) $this->data['user_id'] . "
0914                          AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
0915                  $db->sql_query($sql);
0916              }
0917   
0918              // Reset the data array
0919              $this->data = array();
0920   
0921              $sql = 'SELECT *
0922                  FROM ' . USERS_TABLE . '
0923                  WHERE user_id = ' . ANONYMOUS;
0924              $result = $db->sql_query($sql);
0925              $this->data = $db->sql_fetchrow($result);
0926              $db->sql_freeresult($result);
0927          }
0928   
0929          $cookie_expire = $this->time_now - 31536000;
0930          $this->set_cookie('u', '', $cookie_expire);
0931          $this->set_cookie('k', '', $cookie_expire);
0932          $this->set_cookie('sid', '', $cookie_expire);
0933          unset($cookie_expire);
0934   
0935          $SID = '?sid=';
0936          $this->session_id = $_SID = '';
0937   
0938          // To make sure a valid session is created we create one for the anonymous user
0939          if ($new_session)
0940          {
0941              $this->session_create(ANONYMOUS);
0942          }
0943   
0944          return true;
0945      }
0946   
0947      /**
0948      * Session garbage collection
0949      *
0950      * This looks a lot more complex than it really is. Effectively we are
0951      * deleting any sessions older than an admin definable limit. Due to the
0952      * way in which we maintain session data we have to ensure we update user
0953      * data before those sessions are destroyed. In addition this method
0954      * removes autologin key information that is older than an admin defined
0955      * limit.
0956      */
0957      function session_gc()
0958      {
0959          global $db, $config, $phpbb_container, $phpbb_dispatcher;
0960   
0961          if (!$this->time_now)
0962          {
0963              $this->time_now = time();
0964          }
0965   
0966          /**
0967           * Get expired sessions for registered users, only most recent for each user
0968           * Inner SELECT gets most recent expired sessions for unique session_user_id
0969           * Outer SELECT gets data for them
0970           */
0971          $sql_select = 'SELECT s1.session_page, s1.session_user_id, s1.session_time AS recent_time
0972              FROM ' . SESSIONS_TABLE . ' AS s1
0973              INNER JOIN (
0974                  SELECT session_user_id, MAX(session_time) AS recent_time
0975                  FROM ' . SESSIONS_TABLE . '
0976                  WHERE session_time < ' . ($this->time_now - (int) $config['session_length']) . '
0977                      AND session_user_id <> ' . ANONYMOUS . '
0978                  GROUP BY session_user_id
0979              ) AS s2
0980              ON s1.session_user_id = s2.session_user_id
0981                  AND s1.session_time = s2.recent_time';
0982   
0983          switch ($db->get_sql_layer())
0984          {
0985              case 'sqlite3':
0986                  if (phpbb_version_compare($db->sql_server_info(true), '3.8.3', '>='))
0987                  {
0988                      // For SQLite versions 3.8.3+ which support Common Table Expressions (CTE)
0989                      $sql = "WITH s3 (session_page, session_user_id, session_time) AS ($sql_select)
0990                          UPDATE " . USERS_TABLE . '
0991                          SET (user_lastpage, user_lastvisit) = (SELECT session_page, session_time FROM s3 WHERE session_user_id = user_id)
0992                          WHERE EXISTS (SELECT session_user_id FROM s3 WHERE session_user_id = user_id)';
0993                      $db->sql_query($sql);
0994   
0995                      break;
0996                  }
0997   
0998              // No break, for SQLite versions prior to 3.8.3 and Oracle
0999              case 'oracle':
1000                  $result = $db->sql_query($sql_select);
1001                  while ($row = $db->sql_fetchrow($result))
1002                  {
1003                      $sql = 'UPDATE ' . USERS_TABLE . '
1004                          SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "'
1005                          WHERE user_id = " . (int) $row['session_user_id'];
1006                      $db->sql_query($sql);
1007                  }
1008                  $db->sql_freeresult($result);
1009              break;
1010   
1011              case 'mysqli':
1012                  $sql = 'UPDATE ' . USERS_TABLE . " u,
1013                      ($sql_select) s3
1014                      SET u.user_lastvisit = s3.recent_time, u.user_lastpage = s3.session_page
1015                      WHERE u.user_id = s3.session_user_id";
1016                  $db->sql_query($sql);
1017              break;
1018   
1019              default:
1020                  $sql = 'UPDATE ' . USERS_TABLE . "
1021                      SET user_lastvisit = s3.recent_time, user_lastpage = s3.session_page
1022                      FROM ($sql_select) s3
1023                      WHERE user_id = s3.session_user_id";
1024                  $db->sql_query($sql);
1025              break;
1026          }
1027   
1028          // Delete all expired sessions
1029          $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
1030              WHERE session_time < ' . ($this->time_now - (int) $config['session_length']);
1031          $db->sql_query($sql);
1032   
1033          // Update gc timer
1034          $config->set('session_last_gc', $this->time_now, false);
1035   
1036          if ($config['max_autologin_time'])
1037          {
1038              $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
1039                  WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time']));
1040              $db->sql_query($sql);
1041          }
1042   
1043          // only called from CRON; should be a safe workaround until the infrastructure gets going
1044          /* @var \phpbb\captcha\factory $captcha_factory */
1045          $captcha_factory = $phpbb_container->get('captcha.factory');
1046          $captcha_factory->garbage_collect($config['captcha_plugin']);
1047   
1048          $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . '
1049              WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']);
1050          $db->sql_query($sql);
1051   
1052          /**
1053          * Event to trigger extension on session_gc
1054          *
1055          * @event core.session_gc_after
1056          * @since 3.1.6-RC1
1057          */
1058          $phpbb_dispatcher->dispatch('core.session_gc_after');
1059   
1060          return;
1061      }
1062   
1063      /**
1064      * Sets a cookie
1065      *
1066      * 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.
1067      *
1068      * @param string $name        Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then.
1069      * @param string $cookiedata    The data to hold within the cookie
1070      * @param int $cookietime    The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set.
1071      * @param bool $httponly        Use HttpOnly. Defaults to true. Use false to make cookie accessible by client-side scripts.
1072      */
1073      function set_cookie($name, $cookiedata, $cookietime, $httponly = true)
1074      {
1075          global $config, $phpbb_dispatcher;
1076   
1077          // If headers are already set, we just return
1078          if (headers_sent())
1079          {
1080              return;
1081          }
1082   
1083          $disable_cookie = false;
1084          /**
1085          * Event to modify or disable setting cookies
1086          *
1087          * @event core.set_cookie
1088          * @var    bool        disable_cookie    Set to true to disable setting this cookie
1089          * @var    string        name            Name of the cookie
1090          * @var    string        cookiedata        The data to hold within the cookie
1091          * @var    int            cookietime        The expiration time as UNIX timestamp
1092          * @var    bool        httponly        Use HttpOnly?
1093          * @since 3.2.9-RC1
1094          */
1095          $vars = array(
1096              'disable_cookie',
1097              'name',
1098              'cookiedata',
1099              'cookietime',
1100              'httponly',
1101          );
1102          extract($phpbb_dispatcher->trigger_event('core.set_cookie', compact($vars)));
1103   
1104          if ($disable_cookie)
1105          {
1106              return;
1107          }
1108   
1109          $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata);
1110          $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime);
1111          $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == '127.0.0.1' || strpos($config['cookie_domain'], '.') === false) ? '' : '; domain=' . $config['cookie_domain'];
1112   
1113          header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . ';' . (($httponly) ? ' HttpOnly' : ''), false);
1114      }
1115   
1116      /**
1117      * Check for banned user
1118      *
1119      * Checks whether the supplied user is banned by id, ip or email. If no parameters
1120      * are passed to the method pre-existing session data is used.
1121      *
1122      * @param int|false        $user_id        The user id
1123      * @param mixed            $user_ips        Can contain a string with one IP or an array of multiple IPs
1124      * @param string|false    $user_email        The user email
1125      * @param bool            $return            If $return is false this routine does not return on finding a banned user,
1126      *    it outputs a relevant message and stops execution.
1127      */
1128      function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false)
1129      {
1130          global $config, $db, $phpbb_dispatcher;
1131   
1132          if (defined('IN_CHECK_BAN') || defined('SKIP_CHECK_BAN'))
1133          {
1134              return;
1135          }
1136   
1137          $banned = false;
1138          $cache_ttl = 3600;
1139          $where_sql = array();
1140   
1141          $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end
1142              FROM ' . BANLIST_TABLE . '
1143              WHERE ';
1144   
1145          // Determine which entries to check, only return those
1146          if ($user_email === false)
1147          {
1148              $where_sql[] = "ban_email = ''";
1149          }
1150   
1151          if ($user_ips === false)
1152          {
1153              $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)";
1154          }
1155   
1156          if ($user_id === false)
1157          {
1158              $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)';
1159          }
1160          else
1161          {
1162              $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0;
1163              $_sql = '(ban_userid = ' . $user_id;
1164   
1165              if ($user_email !== false)
1166              {
1167                  $_sql .= " OR ban_email <> ''";
1168              }
1169   
1170              if ($user_ips !== false)
1171              {
1172                  $_sql .= " OR ban_ip <> ''";
1173              }
1174   
1175              $_sql .= ')';
1176   
1177              $where_sql[] = $_sql;
1178          }
1179   
1180          $sql .= (count($where_sql)) ? implode(' AND ', $where_sql) : '';
1181          $result = $db->sql_query($sql, $cache_ttl);
1182   
1183          $ban_triggered_by = 'user';
1184          while ($row = $db->sql_fetchrow($result))
1185          {
1186              if ($row['ban_end'] && $row['ban_end'] < time())
1187              {
1188                  continue;
1189              }
1190   
1191              $ip_banned = false;
1192              if (!empty($row['ban_ip']))
1193              {
1194                  if (!is_array($user_ips))
1195                  {
1196                      $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips);
1197                  }
1198                  else
1199                  {
1200                      foreach ($user_ips as $user_ip)
1201                      {
1202                          if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip))
1203                          {
1204                              $ip_banned = true;
1205                              break;
1206                          }
1207                      }
1208                  }
1209              }
1210   
1211              if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) ||
1212                  $ip_banned ||
1213                  (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email)))
1214              {
1215                  if (!empty($row['ban_exclude']))
1216                  {
1217                      $banned = false;
1218                      break;
1219                  }
1220                  else
1221                  {
1222                      $banned = true;
1223                      $ban_row = $row;
1224   
1225                      if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id)
1226                      {
1227                          $ban_triggered_by = 'user';
1228                      }
1229                      else if ($ip_banned)
1230                      {
1231                          $ban_triggered_by = 'ip';
1232                      }
1233                      else
1234                      {
1235                          $ban_triggered_by = 'email';
1236                      }
1237   
1238                      // Don't break. Check if there is an exclude rule for this user
1239                  }
1240              }
1241          }
1242          $db->sql_freeresult($result);
1243   
1244          /**
1245          * Event to set custom ban type
1246          *
1247          * @event core.session_set_custom_ban
1248          * @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
1249          * @var    bool        banned                Check if user already banned
1250          * @var    array|false    ban_row                Ban data
1251          * @var    string        ban_triggered_by    Method that caused ban, can be your custom method
1252          * @since 3.1.3-RC1
1253          */
1254          $ban_row = isset($ban_row) ? $ban_row : false;
1255          $vars = array('return', 'banned', 'ban_row', 'ban_triggered_by');
1256          extract($phpbb_dispatcher->trigger_event('core.session_set_custom_ban', compact($vars)));
1257   
1258          if ($banned && !$return)
1259          {
1260              global $phpbb_root_path, $phpEx;
1261   
1262              // If the session is empty we need to create a valid one...
1263              if (empty($this->session_id))
1264              {
1265                  // This seems to be no longer needed? - #14971
1266  //                $this->session_create(ANONYMOUS);
1267              }
1268   
1269              // Initiate environment ... since it won't be set at this stage
1270              $this->setup();
1271   
1272              // Logout the user, banned users are unable to use the normal 'logout' link
1273              if ($this->data['user_id'] != ANONYMOUS)
1274              {
1275                  $this->session_kill();
1276              }
1277   
1278              // We show a login box here to allow founders accessing the board if banned by IP
1279              if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS)
1280              {
1281                  $this->setup('ucp');
1282                  $this->data['is_registered'] = $this->data['is_bot'] = false;
1283   
1284                  // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again.
1285                  define('IN_CHECK_BAN', 1);
1286   
1287                  login_box("index.$phpEx");
1288   
1289                  // The false here is needed, else the user is able to circumvent the ban.
1290                  $this->session_kill(false);
1291              }
1292   
1293              // Ok, we catch the case of an empty session id for the anonymous user...
1294              // This can happen if the user is logging in, banned by username and the login_box() being called "again".
1295              if (empty($this->session_id) && defined('IN_CHECK_BAN'))
1296              {
1297                  $this->session_create(ANONYMOUS);
1298              }
1299   
1300              // Determine which message to output
1301              $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : '';
1302              $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM';
1303   
1304              $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx);
1305              $message = sprintf($this->lang[$message], $till_date, '<a href="' . $contact_link . '">', '</a>');
1306              $message .= ($ban_row['ban_give_reason']) ? '<br /><br />' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : '';
1307              $message .= '<br /><br /><em>' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . '</em>';
1308   
1309              // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page
1310              if (defined('IN_CRON'))
1311              {
1312                  garbage_collection();
1313                  exit_handler();
1314                  exit;
1315              }
1316   
1317              // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again
1318              $this->session_kill(false);
1319   
1320              trigger_error($message);
1321          }
1322   
1323          if (!empty($ban_row))
1324          {
1325              $ban_row['ban_triggered_by'] = $ban_triggered_by;
1326          }
1327   
1328          return ($banned && $ban_row) ? $ban_row : $banned;
1329      }
1330   
1331      /**
1332       * Check the current session for bans
1333       *
1334       * @return true if session user is banned.
1335       */
1336      protected function check_ban_for_current_session($config)
1337      {
1338          if (!defined('SKIP_CHECK_BAN') && $this->data['user_type'] != USER_FOUNDER)
1339          {
1340              if (!$config['forwarded_for_check'])
1341              {
1342                  $this->check_ban($this->data['user_id'], $this->ip);
1343              }
1344              else
1345              {
1346                  $ips = explode(' ', $this->forwarded_for);
1347                  $ips[] = $this->ip;
1348                  $this->check_ban($this->data['user_id'], $ips);
1349              }
1350          }
1351      }
1352   
1353      /**
1354      * Check if ip is blacklisted by Spamhaus SBL
1355      *
1356      * Disables DNSBL setting if errors are returned by Spamhaus due to a policy violation.
1357      * https://www.spamhaus.com/product/help-for-spamhaus-public-mirror-users/
1358      *
1359      * @param string         $dnsbl    the blacklist to check against
1360      * @param string|false    $ip        the IPv4 address to check
1361      *
1362      * @return bool true if listed in spamhaus database, false if not
1363      */
1364      function check_dnsbl_spamhaus($dnsbl, $ip = false)
1365      {
1366          global $config, $phpbb_log;
1367   
1368          if ($ip === false)
1369          {
1370              $ip = $this->ip;
1371          }
1372   
1373          // Spamhaus does not support IPv6 addresses.
1374          if (strpos($ip, ':') !== false)
1375          {
1376              return false;
1377          }
1378   
1379          if ($ip)
1380          {
1381              $quads = explode('.', $ip);
1382              $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0];
1383   
1384              $records = dns_get_record($reverse_ip . '.' . $dnsbl . '.', DNS_A);
1385              if (empty($records))
1386              {
1387                  return false;
1388              }
1389              else
1390              {
1391                  $error = false;
1392                  foreach ($records as $record)
1393                  {
1394                      if ($record['ip'] == '127.255.255.254')
1395                      {
1396                          $error = 'LOG_SPAMHAUS_OPEN_RESOLVER';
1397                          break;
1398                      }
1399                      else if ($record['ip'] == '127.255.255.255')
1400                      {
1401                          $error = 'LOG_SPAMHAUS_VOLUME_LIMIT';
1402                          break;
1403                      }
1404                  }
1405   
1406                  if ($error !== false)
1407                  {
1408                      $config->set('check_dnsbl', 0);
1409                      $phpbb_log->add('critical', $this->data['user_id'], $ip, $error);
1410                  }
1411                  else
1412                  {
1413                      // The existence of a non-error A record means it's a hit
1414                      return true;
1415                  }
1416              }
1417          }
1418   
1419          return false;
1420      }
1421   
1422      /**
1423      * Checks if an IPv4 address is in a specified DNS blacklist
1424      *
1425      * Only checks if a record is returned or not.
1426      *
1427      * @param string         $dnsbl    the blacklist to check against
1428      * @param string|false    $ip        the IPv4 address to check
1429      *
1430      * @return bool true if record is returned, false if not
1431      */
1432      function check_dnsbl_ipv4_generic($dnsbl, $ip = false)
1433      {
1434          if ($ip === false)
1435          {
1436              $ip = $this->ip;
1437          }
1438   
1439          // This function does not support IPv6 addresses.
1440          if (strpos($ip, ':') !== false)
1441          {
1442              return false;
1443          }
1444   
1445          $quads = explode('.', $ip);
1446          $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0];
1447   
1448          if (checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true)
1449          {
1450              return true;
1451          }
1452   
1453          return false;
1454      }
1455   
1456      /**
1457      * Check if ip is blacklisted
1458      * This should be called only where absolutely necessary
1459      *
1460      * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups)
1461      *
1462      * @author satmd (from the php manual)
1463      * @param string         $mode    register/post - spamcop for example is omitted for posting
1464      * @param string|false    $ip        the IPv4 address to check
1465      *
1466      * @return false if ip is not blacklisted, else an array([checked server], [lookup])
1467      */
1468      function check_dnsbl($mode, $ip = false)
1469      {
1470          if ($ip === false)
1471          {
1472              $ip = $this->ip;
1473          }
1474   
1475          // Neither Spamhaus nor Spamcop supports IPv6 addresses.
1476          if (strpos($ip, ':') !== false)
1477          {
1478              return false;
1479          }
1480   
1481          $dnsbl_check = array(
1482              'sbl.spamhaus.org'    => ['https://check.spamhaus.org/listed/?searchterm=', 'check_dnsbl_spamhaus'],
1483          );
1484   
1485          if ($mode == 'register')
1486          {
1487              $dnsbl_check['bl.spamcop.net'] = ['https://www.spamcop.net/bl.shtml?', 'check_dnsbl_ipv4_generic'];
1488          }
1489   
1490          if ($ip)
1491          {
1492              // Need to be listed on all servers...
1493              $listed = true;
1494              $info = array();
1495   
1496              foreach ($dnsbl_check as $dnsbl => $lookup)
1497              {
1498                  if (call_user_func(array($this, $lookup[1]), $dnsbl, $ip) === true)
1499                  {
1500                      $info = array($dnsbl, $lookup[0] . $ip);
1501                  }
1502                  else
1503                  {
1504                      $listed = false;
1505                  }
1506              }
1507   
1508              if ($listed)
1509              {
1510                  return $info;
1511              }
1512          }
1513   
1514          return false;
1515      }
1516   
1517      /**
1518      * Check if URI is blacklisted
1519      * This should be called only where absolutely necessary, for example on the submitted website field
1520      * This function is not in use at the moment and is only included for testing purposes, it may not work at all!
1521      * This means it is untested at the moment and therefore commented out
1522      *
1523      * @param string $uri URI to check
1524      * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists
1525      function check_uribl($uri)
1526      {
1527          // Normally parse_url() is not intended to parse uris
1528          // We need to get the top-level domain name anyway... change.
1529          $uri = parse_url($uri);
1530   
1531          if ($uri === false || empty($uri['host']))
1532          {
1533              return false;
1534          }
1535   
1536          $uri = trim($uri['host']);
1537   
1538          if ($uri)
1539          {
1540              // One problem here... the return parameter for the "windows" method is different from what
1541              // we expect... this may render this check useless...
1542              if (checkdnsrr($uri . '.multi.uribl.com.', 'A') === true)
1543              {
1544                  return true;
1545              }
1546          }
1547   
1548          return false;
1549      }
1550      */
1551   
1552      /**
1553      * Set/Update a persistent login key
1554      *
1555      * This method creates or updates a persistent session key. When a user makes
1556      * use of persistent (formerly auto-) logins a key is generated and stored in the
1557      * DB. When they revisit with the same key it's automatically updated in both the
1558      * DB and cookie. Multiple keys may exist for each user representing different
1559      * browsers or locations. As with _any_ non-secure-socket no passphrase login this
1560      * remains vulnerable to exploit.
1561      */
1562      function set_login_key($user_id = false, $key = false, $user_ip = false)
1563      {
1564          global $db, $phpbb_dispatcher;
1565   
1566          $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id;
1567          $user_ip = ($user_ip === false) ? $this->ip : $user_ip;
1568          $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key;
1569   
1570          $key_id = unique_id(hexdec(substr($this->session_id, 0, 8)));
1571   
1572          $sql_ary = array(
1573              'key_id'        => (string) md5($key_id),
1574              'last_ip'        => (string) $user_ip,
1575              'last_login'    => (int) time()
1576          );
1577   
1578          if (!$key)
1579          {
1580              $sql_ary += array(
1581                  'user_id'    => (int) $user_id
1582              );
1583          }
1584   
1585          if ($key)
1586          {
1587              $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . '
1588                  SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
1589                  WHERE user_id = ' . (int) $user_id . "
1590                      AND key_id = '" . $db->sql_escape(md5($key)) . "'";
1591          }
1592          else
1593          {
1594              $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
1595          }
1596   
1597          /**
1598           * Event to adjust autologin keys process
1599           *
1600           * @event core.set_login_key
1601           * @var    string|false    key            Current autologin key if exists, false otherwise
1602           * @var    string            key_id        New autologin key
1603           * @var    string            sql            SQL query to update/insert autologin key
1604           * @var    array            sql_ary        Aray with autologin key data
1605           * @var    int                user_id        Current user's ID
1606           * @var    string            user_ip        Current user's IP address
1607           * @since 3.3.2-RC1
1608           */
1609          $vars = [
1610              'key',
1611              'key_id',
1612              'sql',
1613              'sql_ary',
1614              'user_id',
1615              'user_ip',
1616          ];
1617          extract($phpbb_dispatcher->trigger_event('core.set_login_key', compact($vars)));
1618   
1619          $db->sql_query($sql);
1620   
1621          $this->cookie_data['k'] = $key_id;
1622   
1623          return false;
1624      }
1625   
1626      /**
1627      * Reset all login keys for the specified user
1628      *
1629      * This method removes all current login keys for a specified (or the current)
1630      * user. It will be called on password change to render old keys unusable
1631      */
1632      function reset_login_keys($user_id = false)
1633      {
1634          global $db;
1635   
1636          $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id;
1637   
1638          $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
1639              WHERE user_id = ' . (int) $user_id;
1640          $db->sql_query($sql);
1641   
1642          // If the user is logged in, update last visit info first before deleting sessions
1643          $sql = 'SELECT session_time, session_page
1644              FROM ' . SESSIONS_TABLE . '
1645              WHERE session_user_id = ' . (int) $user_id . '
1646              ORDER BY session_time DESC';
1647          $result = $db->sql_query_limit($sql, 1);
1648          $row = $db->sql_fetchrow($result);
1649          $db->sql_freeresult($result);
1650   
1651          if ($row)
1652          {
1653              $sql = 'UPDATE ' . USERS_TABLE . '
1654                  SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "'
1655                  WHERE user_id = " . (int) $user_id;
1656              $db->sql_query($sql);
1657          }
1658   
1659          // Let's also clear any current sessions for the specified user_id
1660          // If it's the current user then we'll leave this session intact
1661          $sql_where = 'session_user_id = ' . (int) $user_id;
1662          $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : '';
1663   
1664          $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
1665              WHERE $sql_where";
1666          $db->sql_query($sql);
1667   
1668          // We're changing the password of the current user and they have a key
1669          // Lets regenerate it to be safe
1670          if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k'])
1671          {
1672              $this->set_login_key($user_id);
1673          }
1674      }
1675   
1676   
1677      /**
1678      * Check if the request originated from the same page.
1679      * @param bool $check_script_path If true, the path will be checked as well
1680      */
1681      function validate_referer($check_script_path = false)
1682      {
1683          global $config, $request;
1684   
1685          // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason)
1686          if (empty($this->referer) || empty($this->host))
1687          {
1688              return true;
1689          }
1690   
1691          $host = htmlspecialchars($this->host, ENT_COMPAT);
1692          $ref = substr($this->referer, strpos($this->referer, '://') + 3);
1693   
1694          if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0)))
1695          {
1696              return false;
1697          }
1698          else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '')
1699          {
1700              $ref = substr($ref, strlen($host));
1701              $server_port = $request->server('SERVER_PORT', 0);
1702   
1703              if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0)
1704              {
1705                  $ref = substr($ref, strlen(":$server_port"));
1706              }
1707   
1708              if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0))
1709              {
1710                  return false;
1711              }
1712          }
1713   
1714          return true;
1715      }
1716   
1717   
1718      function unset_admin()
1719      {
1720          global $db;
1721          $sql = 'UPDATE ' . SESSIONS_TABLE . '
1722              SET session_admin = 0
1723              WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'';
1724          $db->sql_query($sql);
1725      }
1726   
1727      /**
1728      * Update the session data
1729      *
1730      * @param array $session_data associative array of session keys to be updated
1731      * @param string $session_id optional session_id, defaults to current user's session_id
1732      */
1733      public function update_session($session_data, $session_id = null)
1734      {
1735          global $db, $phpbb_dispatcher;
1736   
1737          $session_id = ($session_id) ? $session_id : $this->session_id;
1738   
1739          $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $session_data) . "
1740              WHERE session_id = '" . $db->sql_escape($session_id) . "'";
1741          $db->sql_query($sql);
1742   
1743          /**
1744          * Event to send update session information to extension
1745          * Read-only event
1746          *
1747          * @event core.update_session_after
1748          * @var    array        session_data                Associative array of session keys to be updated
1749          * @var    string        session_id                current user's session_id
1750          * @since 3.1.6-RC1
1751          */
1752          $vars = array('session_data', 'session_id');
1753          extract($phpbb_dispatcher->trigger_event('core.update_session_after', compact($vars)));
1754      }
1755   
1756      public function update_session_infos()
1757      {
1758          global $config, $db, $request;
1759   
1760          // No need to update if it's a new session. Informations are already inserted by session_create()
1761          if (isset($this->data['session_created']) && $this->data['session_created'])
1762          {
1763              return;
1764          }
1765   
1766          // Do not update the session page for ajax requests, so the view online still works as intended
1767          $page_changed = $this->update_session_page && (!isset($this->data['session_page']) || $this->data['session_page'] != $this->page['page'] || $this->data['session_forum_id'] != $this->page['forum']) && !$request->is_ajax();
1768   
1769          // Only update session DB a minute or so after last update or if page changes
1770          if ($this->time_now - (isset($this->data['session_time']) ? $this->data['session_time'] : 0) > 60 || $page_changed)
1771          {
1772              $sql_ary = array('session_time' => $this->time_now);
1773   
1774              if ($page_changed)
1775              {
1776                  $sql_ary['session_page'] = substr($this->page['page'], 0, 199);
1777                  $sql_ary['session_forum_id'] = $this->page['forum'];
1778              }
1779   
1780              $db->sql_return_on_error(true);
1781   
1782              $this->update_session($sql_ary);
1783   
1784              $db->sql_return_on_error(false);
1785   
1786              $this->data = array_merge($this->data, $sql_ary);
1787   
1788              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'])
1789              {
1790                  $this->leave_newly_registered();
1791              }
1792          }
1793      }
1794   
1795      /**
1796       * Get user ID
1797       *
1798       * @return int User ID
1799       */
1800      public function id() : int
1801      {
1802          return isset($this->data['user_id']) ? (int) $this->data['user_id'] : ANONYMOUS;
1803      }
1804   
1805      /**
1806       * Update user last visit time
1807       */
1808      public function update_user_lastvisit()
1809      {
1810          global $db;
1811   
1812          if (isset($this->data['session_time'], $this->data['user_id']))
1813          {
1814              $sql = 'UPDATE ' . USERS_TABLE . '
1815                  SET user_lastvisit = ' . (int) $this->data['session_time'] . ',
1816                      user_last_active = ' . $this->time_now . '
1817                  WHERE user_id = ' . (int) $this->data['user_id'];
1818              $db->sql_query($sql);
1819          }
1820      }
1821   
1822      /**
1823       * Update user's last active time
1824       *
1825       * @return void
1826       */
1827      public function update_last_active_time()
1828      {
1829          global $db;
1830   
1831          if (isset($this->time_now, $this->data['user_id']))
1832          {
1833              $sql = 'UPDATE ' . USERS_TABLE . '
1834                  SET user_last_active = ' . $this->time_now . '
1835                  WHERE user_id = ' . (int) $this->data['user_id'];
1836              $db->sql_query($sql);
1837          }
1838      }
1839  }
1840