1: <?php
2: /**
3: * XOOPS session handler
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package kernel
15: * @since 2.0.0
16: * @author Kazumi Ono (AKA onokazu) http://www.myweb.ne.jp/, http://jp.xoops.org/
17: * @author Taiwen Jiang <phppp@users.sourceforge.net>
18: */
19:
20: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21:
22: /**
23: * Handler for a session
24: * @package kernel
25: *
26: * @author Kazumi Ono <onokazu@xoops.org>
27: * @author Taiwen Jiang <phppp@users.sourceforge.net>
28: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
29: */
30: class XoopsSessionHandler
31: {
32: /**
33: * Database connection
34: *
35: * @var object
36: * @access private
37: */
38: public $db;
39:
40: /**
41: * Security checking level
42: *
43: * Possible value:
44: * 0 - no check;
45: * 1 - check browser characteristics (HTTP_USER_AGENT/HTTP_ACCEPT_LANGUAGE), to be implemented in the future now;
46: * 2 - check browser and IP A.B;
47: * 3 - check browser and IP A.B.C, recommended;
48: * 4 - check browser and IP A.B.C.D;
49: *
50: * @var int
51: * @access public
52: */
53: public $securityLevel = 3;
54:
55: protected $bitMasks = array(
56: 2 => array('v4' => 16, 'v6' => 64),
57: 3 => array('v4' => 24, 'v6' => 56),
58: 4 => array('v4' => 32, 'v6' => 128),
59: );
60:
61: /**
62: * Enable regenerate_id
63: *
64: * @var bool
65: * @access public
66: */
67: public $enableRegenerateId = true;
68:
69: /**
70: * Constructor
71: *
72: * @param XoopsDatabase $db reference to the {@link XoopsDatabase} object
73: *
74: */
75: public function __construct(XoopsDatabase $db)
76: {
77: global $xoopsConfig;
78:
79: $this->db = $db;
80: // after php 7.3 we just let php handle the session cookie
81: $lifetime = ($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
82: ? $xoopsConfig['session_expire'] * 60
83: : ini_get('session.cookie_lifetime');
84: $secure = (XOOPS_PROT === 'https://');
85: if (PHP_VERSION_ID >= 70300) {
86: $options = array(
87: 'lifetime' => $lifetime,
88: 'path' => '/',
89: 'domain' => XOOPS_COOKIE_DOMAIN,
90: 'secure' => $secure,
91: 'httponly' => true,
92: 'samesite' => 'strict',
93: );
94: session_set_cookie_params($options);
95: } else {
96: session_set_cookie_params($lifetime, '/', XOOPS_COOKIE_DOMAIN, $secure, true);
97: }
98: }
99:
100: /**
101: * Open a session
102: *
103: * @param string $savePath
104: * @param string $sessionName
105: *
106: * @return bool
107: */
108: public function open($savePath, $sessionName)
109: {
110: return true;
111: }
112:
113: /**
114: * Close a session
115: *
116: * @return bool
117: */
118: public function close()
119: {
120: $this->gc_force();
121:
122: return true;
123: }
124:
125: /**
126: * Read a session from the database
127: *
128: * @param string $sessionId ID of the session
129: *
130: * @return string Session data
131: */
132: public function read($sessionId)
133: {
134: $ip = \Xmf\IPAddress::fromRequest();
135: $sql = sprintf(
136: 'SELECT sess_data, sess_ip FROM %s WHERE sess_id = %s',
137: $this->db->prefix('session'),
138: $this->db->quoteString($sessionId)
139: );
140:
141: $result = $this->db->query($sql);
142: if ($this->db->isResultSet($result)) {
143: if (list($sess_data, $sess_ip) = $this->db->fetchRow($result)) {
144: if ($this->securityLevel > 1) {
145: if (false === $ip->sameSubnet(
146: $sess_ip,
147: $this->bitMasks[$this->securityLevel]['v4'],
148: $this->bitMasks[$this->securityLevel]['v6']
149: )) {
150: $sess_data = '';
151: }
152: }
153:
154: return $sess_data;
155: }
156: }
157:
158: return '';
159: }
160:
161: /**
162: * Write a session to the database
163: *
164: * @param string $sessionId
165: * @param string $data
166: *
167: * @return bool
168: **/
169: public function write($sessionId, $data)
170: {
171: $myReturn = true;
172: $remoteAddress = \Xmf\IPAddress::fromRequest()->asReadable();
173: $sessionId = $this->db->quoteString($sessionId);
174: $sql = sprintf(
175: 'UPDATE %s SET sess_updated = %u, sess_data = %s WHERE sess_id = %s',
176: $this->db->prefix('session'),
177: time(),
178: $this->db->quoteString($data),
179: $sessionId
180: );
181: $this->db->queryF($sql);
182: if (!$this->db->getAffectedRows()) {
183: $sql = sprintf(
184: 'INSERT INTO %s (sess_id, sess_updated, sess_ip, sess_data) VALUES (%s, %u, %s, %s)',
185: $this->db->prefix('session'),
186: $sessionId,
187: time(),
188: $this->db->quote($remoteAddress),
189: $this->db->quote($data)
190: );
191:
192: $myReturn = $this->db->queryF($sql);
193: }
194: $this->update_cookie();
195: return $myReturn;
196: }
197:
198: /**
199: * Destroy a session
200: *
201: * @param string $sessionId
202: *
203: * @return bool
204: **/
205: public function destroy($sessionId)
206: {
207: $sql = sprintf(
208: 'DELETE FROM %s WHERE sess_id = %s',
209: $this->db->prefix('session'),
210: $this->db->quoteString($sessionId)
211: );
212: if (!$result = $this->db->queryF($sql)) {
213: return false;
214: }
215:
216: return true;
217: }
218:
219: /**
220: * Garbage Collector
221: *
222: * @param int $expire Time in seconds until a session expires
223: * @return bool
224: **/
225: public function gc($expire)
226: {
227: if (empty($expire)) {
228: return true;
229: }
230:
231: $mintime = time() - (int)$expire;
232: $sql = sprintf('DELETE FROM %s WHERE sess_updated < %u', $this->db->prefix('session'), $mintime);
233:
234: return $this->db->queryF($sql);
235: }
236:
237: /**
238: * Force gc for situations where gc is registered but not executed
239: **/
240: public function gc_force()
241: {
242: if (mt_rand(1, 100) < 11) {
243: $expire = @ini_get('session.gc_maxlifetime');
244: $expire = ($expire > 0) ? $expire : 900;
245: $this->gc($expire);
246: }
247: }
248:
249: /**
250: * Update the current session id with a newly generated one
251: *
252: * To be refactored
253: *
254: * @param bool $delete_old_session
255: * @return bool
256: **/
257: public function regenerate_id($delete_old_session = false)
258: {
259: if (!$this->enableRegenerateId) {
260: $success = true;
261: } else {
262: $success = session_regenerate_id($delete_old_session);
263: }
264:
265: // Force updating cookie for session cookie
266: if ($success) {
267: $this->update_cookie();
268: }
269:
270: return $success;
271: }
272:
273: /**
274: * Update cookie status for current session
275: *
276: * To be refactored
277: * FIXME: how about $xoopsConfig['use_ssl'] is enabled?
278: *
279: * @param string $sess_id session ID
280: * @param int $expire Time in seconds until a session expires
281: * @return bool
282: **/
283: public function update_cookie($sess_id = null, $expire = null)
284: {
285: if (PHP_VERSION_ID < 70300) {
286: global $xoopsConfig;
287: $session_name = session_name();
288: $session_expire = null !== $expire
289: ? (int)$expire
290: : (($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
291: ? $xoopsConfig['session_expire'] * 60
292: : ini_get('session.cookie_lifetime')
293: );
294: $session_id = empty($sess_id) ? session_id() : $sess_id;
295: $cookieDomain = XOOPS_COOKIE_DOMAIN;
296: if (2 > substr_count($cookieDomain, '.')) {
297: $cookieDomain = '.' . $cookieDomain ;
298: }
299:
300: xoops_setcookie(
301: $session_name,
302: $session_id,
303: $session_expire ? time() + $session_expire : 0,
304: '/',
305: $cookieDomain,
306: (XOOPS_PROT === 'https://'),
307: true
308: );
309: }
310: }
311: }
312: