Session.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. /**
  3. * Implementation of PHP's session API.
  4. * What it does:
  5. * - it handles the session data in the database (more scalable.)
  6. * - it uses the databaseSession_lifetime setting for garbage collection.
  7. * - the custom session handler is set by loadSession().
  8. *
  9. * Simple Machines Forum (SMF)
  10. *
  11. * @package SMF
  12. * @author Simple Machines http://www.simplemachines.org
  13. * @copyright 2011 Simple Machines
  14. * @license http://www.simplemachines.org/about/smf/license.php BSD
  15. *
  16. * @version 2.1 Alpha 1
  17. */
  18. if (!defined('SMF'))
  19. die('Hacking attempt...');
  20. /**
  21. * Attempt to start the session, unless it already has been.
  22. */
  23. function loadSession()
  24. {
  25. global $HTTP_SESSION_VARS, $modSettings, $boardurl, $sc;
  26. // Attempt to change a few PHP settings.
  27. @ini_set('session.use_cookies', true);
  28. @ini_set('session.use_only_cookies', false);
  29. @ini_set('url_rewriter.tags', '');
  30. @ini_set('session.use_trans_sid', false);
  31. @ini_set('arg_separator.output', '&amp;');
  32. if (!empty($modSettings['globalCookies']))
  33. {
  34. $parsed_url = parse_url($boardurl);
  35. if (preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
  36. @ini_set('session.cookie_domain', '.' . $parts[1]);
  37. }
  38. // @todo Set the session cookie path?
  39. // If it's already been started... probably best to skip this.
  40. if ((ini_get('session.auto_start') == 1 && !empty($modSettings['databaseSession_enable'])) || session_id() == '')
  41. {
  42. // Attempt to end the already-started session.
  43. if (ini_get('session.auto_start') == 1)
  44. session_write_close();
  45. // This is here to stop people from using bad junky PHPSESSIDs.
  46. if (isset($_REQUEST[session_name()]) && preg_match('~^[A-Za-z0-9]{16,32}$~', $_REQUEST[session_name()]) == 0 && !isset($_COOKIE[session_name()]))
  47. {
  48. $session_id = md5(md5('smf_sess_' . time()) . mt_rand());
  49. $_REQUEST[session_name()] = $session_id;
  50. $_GET[session_name()] = $session_id;
  51. $_POST[session_name()] = $session_id;
  52. }
  53. // Use database sessions? (they don't work in 4.1.x!)
  54. if (!empty($modSettings['databaseSession_enable']) && version_compare(PHP_VERSION, '4.2.0', '>='))
  55. {
  56. session_set_save_handler('sessionOpen', 'sessionClose', 'sessionRead', 'sessionWrite', 'sessionDestroy', 'sessionGC');
  57. @ini_set('session.gc_probability', '1');
  58. }
  59. elseif (ini_get('session.gc_maxlifetime') <= 1440 && !empty($modSettings['databaseSession_lifetime']))
  60. @ini_set('session.gc_maxlifetime', max($modSettings['databaseSession_lifetime'], 60));
  61. // Use cache setting sessions?
  62. if (empty($modSettings['databaseSession_enable']) && !empty($modSettings['cache_enable']) && php_sapi_name() != 'cli')
  63. {
  64. if (function_exists('mmcache_set_session_handlers'))
  65. mmcache_set_session_handlers();
  66. elseif (function_exists('eaccelerator_set_session_handlers'))
  67. eaccelerator_set_session_handlers();
  68. }
  69. session_start();
  70. // Change it so the cache settings are a little looser than default.
  71. if (!empty($modSettings['databaseSession_loose']))
  72. header('Cache-Control: private');
  73. }
  74. // While PHP 4.1.x should use $_SESSION, it seems to need this to do it right.
  75. if (version_compare(PHP_VERSION, '4.2.0', '<'))
  76. $HTTP_SESSION_VARS['php_412_bugfix'] = true;
  77. // Set the randomly generated code.
  78. if (!isset($_SESSION['session_var']))
  79. {
  80. $_SESSION['session_value'] = md5(session_id() . mt_rand());
  81. $_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', sha1(mt_rand() . session_id() . mt_rand())), 0, rand(7, 12));
  82. }
  83. $sc = $_SESSION['session_value'];
  84. }
  85. /**
  86. * Implementation of sessionOpen() replacing the standard open handler.
  87. * It simply returns true.
  88. *
  89. * @param string $save_path
  90. * @param string $session_name
  91. * @return bool
  92. */
  93. function sessionOpen($save_path, $session_name)
  94. {
  95. return true;
  96. }
  97. /**
  98. * Implementation of sessionClose() replacing the standard close handler.
  99. * It simply returns true.
  100. *
  101. * @return bool
  102. */
  103. function sessionClose()
  104. {
  105. return true;
  106. }
  107. /**
  108. * Implementation of sessionRead() replacing the standard read handler.
  109. *
  110. * @param string $session_id
  111. * @return string
  112. */
  113. function sessionRead($session_id)
  114. {
  115. global $smcFunc;
  116. if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0)
  117. return false;
  118. // Look for it in the database.
  119. $result = $smcFunc['db_query']('', '
  120. SELECT data
  121. FROM {db_prefix}sessions
  122. WHERE session_id = {string:session_id}
  123. LIMIT 1',
  124. array(
  125. 'session_id' => $session_id,
  126. )
  127. );
  128. list ($sess_data) = $smcFunc['db_fetch_row']($result);
  129. $smcFunc['db_free_result']($result);
  130. return $sess_data;
  131. }
  132. /**
  133. * Implementation of sessionWrite() replacing the standard write handler.
  134. *
  135. * @param string $session_id
  136. * @param string $data
  137. * @return bool
  138. */
  139. function sessionWrite($session_id, $data)
  140. {
  141. global $smcFunc;
  142. if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0)
  143. return false;
  144. // First try to update an existing row...
  145. $result = $smcFunc['db_query']('', '
  146. UPDATE {db_prefix}sessions
  147. SET data = {string:data}, last_update = {int:last_update}
  148. WHERE session_id = {string:session_id}',
  149. array(
  150. 'last_update' => time(),
  151. 'data' => $data,
  152. 'session_id' => $session_id,
  153. )
  154. );
  155. // If that didn't work, try inserting a new one.
  156. if ($smcFunc['db_affected_rows']() == 0)
  157. $result = $smcFunc['db_insert']('ignore',
  158. '{db_prefix}sessions',
  159. array('session_id' => 'string', 'data' => 'string', 'last_update' => 'int'),
  160. array($session_id, $data, time()),
  161. array('session_id')
  162. );
  163. return $result;
  164. }
  165. /**
  166. * Implementation of sessionDestroy() replacing the standard destroy handler.
  167. *
  168. * @param string $session_id
  169. * @return bool
  170. */
  171. function sessionDestroy($session_id)
  172. {
  173. global $smcFunc;
  174. if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0)
  175. return false;
  176. // Just delete the row...
  177. return $smcFunc['db_query']('', '
  178. DELETE FROM {db_prefix}sessions
  179. WHERE session_id = {string:session_id}',
  180. array(
  181. 'session_id' => $session_id,
  182. )
  183. );
  184. }
  185. /**
  186. * Implementation of sessionDestroy() replacing the standard gc handler.
  187. * Callback for garbage collection.
  188. *
  189. * @param int $max_lifetime
  190. * @return bool
  191. */
  192. function sessionGC($max_lifetime)
  193. {
  194. global $modSettings, $smcFunc;
  195. // Just set to the default or lower? Ignore it for a higher value. (hopefully)
  196. if (!empty($modSettings['databaseSession_lifetime']) && ($max_lifetime <= 1440 || $modSettings['databaseSession_lifetime'] > $max_lifetime))
  197. $max_lifetime = max($modSettings['databaseSession_lifetime'], 60);
  198. // Clean up after yerself ;).
  199. return $smcFunc['db_query']('', '
  200. DELETE FROM {db_prefix}sessions
  201. WHERE last_update < {int:last_update}',
  202. array(
  203. 'last_update' => time() - $max_lifetime,
  204. )
  205. );
  206. }