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