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