Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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