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