Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

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