Fixed bug #17732: Install Tool doing Fatal error when APC PHP module is loaded (no...
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / mod / class.tx_install_session.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009-2011 Ernesto Baschny <ernst@cron-it.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Secure session handling for the install tool.
30 *
31 * @author Ernesto Baschny <ernst@cron-it.de>
32 *
33 * @package TYPO3
34 * @subpackage tx_install
35 *
36 * @version $Id$
37 */
38 class tx_install_session {
39
40 /**
41 * The path to our typo3temp (where we can write our sessions). Set in the
42 * constructor.
43 *
44 * @var string
45 */
46 private $typo3tempPath;
47
48 /**
49 * Path where to store our session files in typo3temp. %s will be
50 * non-guesseable.
51 *
52 * @var string
53 */
54 private $sessionPath = 'sessions%s';
55
56 /**
57 * the cookie to store the session ID of the install tool
58 *
59 * @var string
60 */
61 private $cookieName = 'Typo3InstallTool';
62
63 /**
64 * time (minutes) to expire an ununsed session
65 *
66 * @var integer
67 */
68 private $expireTimeInMinutes = 60;
69
70 /**
71 * time (minutes) to generate a new session id for our current session
72 *
73 * @var integer
74 */
75 private $regenerateSessionIdTime = 5;
76
77 /**
78 * part of the referer when the install tool has been called from the backend
79 *
80 * @var string
81 */
82 private $backendFile = 'backend.php';
83
84 /**
85 * Constructor. Starts PHP session handling in our own private store
86 *
87 * Side-effect: might set a cookie, so must be called before any other output.
88 */
89 public function __construct() {
90 $this->typo3tempPath = PATH_site . 'typo3temp/';
91
92 // Start our PHP session early so that hasSession() works
93 $sessionSavePath = $this->getSessionSavePath();
94 if (!is_dir($sessionSavePath)) {
95 if (!t3lib_div::mkdir($sessionSavePath)) {
96 throw new Exception('<p><strong>Could not create session folder in typo3temp/.</strong></p><p>Make sure it is writeable!</p>');
97 }
98 t3lib_div::writeFile($sessionSavePath.'/.htaccess', 'Order deny, allow'."\n".'Deny from all'."\n");
99 $indexContent = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">';
100 $indexContent .= '<HTML><HEAD<TITLE></TITLE><META http-equiv=Refresh Content="0; Url=../../">';
101 $indexContent .= '</HEAD></HTML>';
102 t3lib_div::writeFile($sessionSavePath.'/index.html', $indexContent);
103 }
104 // Register our "save" session handler
105 session_set_save_handler(
106 array($this, 'open'),
107 array($this, 'close'),
108 array($this, 'read'),
109 array($this, 'write'),
110 array($this, 'destroy'),
111 array($this, 'gc')
112 );
113 session_save_path($sessionSavePath);
114 session_name($this->cookieName);
115 ini_set('session.cookie_path', t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
116 // Always call the garbage collector to clean up stale session files
117 ini_set('session.gc_probability', 100);
118 ini_set('session.gc_divisor', 100);
119 ini_set('session.gc_maxlifetime', $this->expireTimeInMinutes*2*60);
120 if (version_compare(phpversion(), '5.2', '<')) {
121 ini_set('session.cookie_httponly', TRUE);
122 }
123 if (ini_get('session.auto_start')) {
124 $sessionCreationError = '<p><strong>Error: session.auto-start is enabled</strong></p>';
125 $sessionCreationError .= '<p>The PHP option session.auto-start is enabled. Disable this option in php.ini or .htaccess:</p>';
126 $sessionCreationError .= '<pre>php_value session.auto_start Off</pre>';
127 throw new Exception($sessionCreationError);
128 } else if (defined('SID')) {
129 $sessionCreationError = '<p><strong>Error: Session already started by session_start().</strong></p>';
130 $sessionCreationError .= '<p>Make sure no installed extension is starting a session in its ext_localconf.php or ext_tables.php.</p>';
131 throw new Exception($sessionCreationError);
132 }
133 session_start();
134 }
135
136 /**
137 * Returns the path where to store our session files
138 */
139 private function getSessionSavePath() {
140 return sprintf(
141 $this->typo3tempPath . $this->sessionPath,
142 md5(
143 'session:' .
144 $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']
145 )
146 );
147 }
148
149 /**
150 * Starts a new session
151 *
152 * @return string The session ID
153 */
154 public function startSession() {
155 $_SESSION['created'] = time();
156 // Be sure to use our own session id, so create a new one
157 return $this->renewSession();
158 }
159
160 /**
161 * Destroys a session
162 *
163 */
164 public function destroySession() {
165 session_destroy();
166 }
167
168 /**
169 * Generates a new session ID and sends it to the client.
170 *
171 * Also moves session information from the old session to the new one
172 * (in PHP 5.1 or later)
173 *
174 * @return string the new session ID
175 */
176 private function renewSession() {
177 if (version_compare(phpversion(), '5.1', '<')) {
178 session_regenerate_id(TRUE);
179 } else {
180 session_regenerate_id();
181 }
182 return session_id();
183 }
184
185 /**
186 * Checks whether we already have an active session.
187 *
188 * @return boolean true if there is an active session, false otherwise
189 */
190 public function hasSession() {
191 return (isset($_SESSION['created']));
192 }
193
194 /**
195 * Returns the session ID of the running session.
196 *
197 * @return string the session ID
198 */
199 public function getSessionId() {
200 return session_id();
201 }
202
203 /**
204 * Returns a session hash, which can only be calculated by the server.
205 * Used to store our session files without exposing the session ID.
206 *
207 * @param string An alternative session ID. Defaults to our current session ID
208 *
209 * @return string the session hash
210 */
211 private function getSessionHash($sessionId = '') {
212 if (!$sessionId) {
213 $sessionId = $this->getSessionId();
214 }
215 return md5($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'].'|'.$sessionId);
216 }
217
218 /**
219 * Marks this session as an "authorized" one (login successful).
220 * Should only be called if:
221 * a) we have a valid session running
222 * b) the "password" or some other authorization mechanism really matched
223 *
224 * @return void
225 */
226 public function setAuthorized() {
227 $_SESSION['authorized'] = TRUE;
228 $_SESSION['lastSessionId'] = time();
229 $_SESSION['tstamp'] = time();
230 $_SESSION['expires'] = (time() + ($this->expireTimeInMinutes*60));
231 // Renew the session id to avoid session fixation
232 $this->renewSession();
233 }
234
235 /**
236 * Check if we have an already authorized session
237 *
238 * @return boolean True if this session has been authorized before (by a correct password)
239 */
240 public function isAuthorized() {
241 if (!$_SESSION['authorized']) {
242 return FALSE;
243 }
244 if ($_SESSION['expires'] < time()) {
245 // This session has already expired
246 return FALSE;
247 }
248 return TRUE;
249 }
250
251 /**
252 * Check if our session is expired.
253 * Useful only right after a false "isAuthorized" to see if this is the
254 * reason for not being authorized anymore.
255 *
256 * @return boolean True if an authorized session exists, but is expired
257 */
258 public function isExpired() {
259 if (!$_SESSION['authorized']) {
260 // Session never existed, means it is not "expired"
261 return FALSE;
262 }
263 if ($_SESSION['expires'] < time()) {
264 // This session was authorized before, but has expired
265 return TRUE;
266 }
267 return FALSE;
268 }
269
270 /**
271 * Refreshes our session information, rising the expire time.
272 * Also generates a new session ID every 5 minutes to minimize the risk of
273 * session hijacking.
274 *
275 * @return void
276 */
277 public function refreshSession() {
278 $_SESSION['tstamp'] = time();
279 $_SESSION['expires'] = time() + ($this->expireTimeInMinutes*60);
280 if (time() > $_SESSION['lastSessionId']+$this->regenerateSessionIdTime*60) {
281 // Renew our session ID
282 $_SESSION['lastSessionId'] = time();
283 $this->renewSession();
284 }
285 }
286
287
288 /*************************
289 *
290 * PHP session handling with "secure" session files (hashed session id)
291 * see http://www.php.net/manual/en/function.session-set-save-handler.php
292 *
293 *************************/
294
295 /**
296 * Returns the file where to store our session data
297 *
298 * @return string A filename
299 */
300 private function getSessionFile($id) {
301 $sessionSavePath = $this->getSessionSavePath();
302 return $sessionSavePath . '/hash_' . $this->getSessionHash($id);
303 }
304
305 /**
306 * Open function. See @session_set_save_handler
307 *
308 * @param string $savePath
309 * @param string $sessionName
310 * @return boolean
311 */
312 public function open($savePath, $sessionName) {
313 return TRUE;
314 }
315
316 /**
317 * Close function. See @session_set_save_handler
318 *
319 * @return boolean
320 */
321 public function close() {
322 return TRUE;
323 }
324
325 /**
326 * Read session data. See @session_set_save_handler
327 *
328 * @param string The session id
329 *
330 * @return string
331 */
332 public function read($id) {
333 $sessionFile = $this->getSessionFile($id);
334 return (string) @file_get_contents($sessionFile);
335 }
336
337 /**
338 * Write session data. See @session_set_save_handler
339 *
340 * @param string The session id
341 * @param string The data to be stored
342 *
343 * @return boolean
344 */
345 public function write($id, $sessionData) {
346 $sessionFile = $this->getSessionFile($id);
347 return t3lib_div::writeFile($sessionFile, $sessionData);
348 }
349
350 /**
351 * Destroys one session. See @session_set_save_handler
352 *
353 * @param string The session id
354 *
355 * @return string
356 */
357 public function destroy($id) {
358 $sessionFile = $this->getSessionFile($id);
359 return(@unlink($sessionFile));
360 }
361
362 /**
363 * Garbage collect session info. See @session_set_save_handler
364 *
365 * @param integer The setting of session.gc_maxlifetime
366 *
367 * @return boolean
368 */
369 public function gc($maxLifeTime) {
370 $sessionSavePath = $this->getSessionSavePath();
371 $files = glob($sessionSavePath . '/hash_*');
372 if (!is_array($files)) {
373 return TRUE;
374 }
375 foreach ($files as $filename) {
376 if (filemtime($filename) + ($this->expireTimeInMinutes*60) < time()) {
377 @unlink($filename);
378 }
379 }
380 return TRUE;
381 }
382
383 /**
384 * Writes the session data at the end, to overcome a PHP APC bug.
385 *
386 * Writes the session data in a proper context that is not affected by the APC bug:
387 * http://pecl.php.net/bugs/bug.php?id=16721.
388 *
389 * This behaviour was introduced in #17511, where self::write() made use of t3lib_div
390 * which due to the APC bug throws a "Fatal error: Class 't3lib_div' not found"
391 * (and the session data is not saved). Calling session_write_close() at this point
392 * seems to be the most easy solution, acording to PHP author.
393 *
394 * @return void
395 */
396 public function __destruct() {
397 session_write_close();
398 }
399 }
400
401 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_session.php'])) {
402 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_session.php']);
403 }
404
405 ?>