MDL-16613 sesskey cleanup
[moodle.git] / lib / sessionlib.php
CommitLineData
57f7b7ce 1<?php //$Id$
2
3/**
4 * Class handling all session and cookies related stuff.
5 */
6class moodle_session {
7 private $session;
8
9 function __construct() {
10 global $CFG;
11
12 $this->prepare_cookies();
13 $this->init_session_storage();
14
15 if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
16 $this->sid_start_ob();
17 }
18
19 if (!NO_MOODLE_COOKIES) {
20 session_name('MoodleSession'.$CFG->sessioncookie);
e6e13284 21 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 22 @session_start();
23 if (!isset($_SESSION['SESSION'])) {
24 $_SESSION['SESSION'] = new object();
25 $_SESSION['SESSION']->session_test = random_string(10);
26 if (!empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie])) {
27 $_SESSION['SESSION']->has_timed_out = true;
28 }
e6e13284 29 setcookie('MoodleSessionTest'.$CFG->sessioncookie, $_SESSION['SESSION']->session_test, 0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 30 $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] = $_SESSION['SESSION']->session_test;
31 }
32 if (!isset($_SESSION['USER'])) {
33 $_SESSION['USER'] = new object();
34 }
35
36 if (!isset($_SESSION['USER']->id)) {
37 $_SESSION['USER']->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
38 if (isset($CFG->mnet_localhost_id)) {
39 $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
40 }
41 }
42
43 $this->session = null;
44
45 } else {
46 $this->session = new object();
47 }
48 }
49
50 /**
51 * Verify session, this detects problems with "switched" sessions
52 * or multiple different wwwroot used at the same time.
53 */
54 public function session_verify() {
55 global $CFG;
56
57 /// disable checks when working in cookieless mode
58 if (empty($CFG->usesid) || !empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
59 if ($this->session != NULL) {
60 if (empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie])) {
61 $this->report_session_error();
62 } else if (isset($this->session->session_test) && $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] != $this->session->session_test) {
63 $this->report_session_error();
64 }
65 }
66 }
67 }
68
69 /**
70 * Report serious problem detected in suer session
71 */
72 function report_session_error() {
73 global $CFG, $FULLME;
74
75 if (empty($CFG->lang)) {
76 $CFG->lang = "en";
77 }
78 // Set up default theme and locale
79 theme_setup();
80 moodle_setlocale();
81
82 //clear session cookies
e6e13284 83 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
84 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 85
86 //increment database error counters
87 if (isset($CFG->session_error_counter)) {
88 set_config('session_error_counter', 1 + $CFG->session_error_counter);
89 } else {
90 set_config('session_error_counter', 1);
91 }
92 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
93 }
94
95 /**
96 * Terminates active moodle session
97 */
98 public function terminate() {
99 global $CFG, $SESSION, $USER;
100
101 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
102 $file = null;
103 $line = null;
104 if (headers_sent($file, $line)) {
105 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
106 error_log('Headers were already sent in file: '.$file.' on line '.$line);
107 } else {
e6e13284 108 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 109 }
110
111 $this->session = new object();
112 $_SESSION = array();
113
114 $USER = new object();
115 $USER->id = 0;
116 if (isset($CFG->mnet_localhost_id)) {
117 $USER->mnethostid = $CFG->mnet_localhost_id;
118 }
119
120 @session_write_close();
121 }
122
123
124 public function __set($name, $value) {
125 if (!is_null($this->session)) {
126 $this->session->{$name} = $value;
127 } else {
128 $_SESSION['SESSION']->{$name} = $value;
129 }
130 }
131
132 public function &__get($name) { // this is a weird hack for this stupid bug http://bugs.php.net/bug.php?id=39449
133 if (!is_null($this->session)) {
134 return $this->session->{$name};
135 } else {
136 return $_SESSION['SESSION']->{$name};
137 }
138 }
139
140 public function __isset($name) {
141 if (!is_null($this->session)) {
142 return isset($this->session->{$name});
143 } else {
144 return isset($_SESSION['SESSION']->{$name});
145 }
146 }
147
148 public function __unset($name) {
149 if (!is_null($this->session)) {
150 unset($this->session->{$name});
151 } else {
152 unset($_SESSION['SESSION']->{$name});
153 }
154 }
155
156 /**
157 * Prepare cookies and varions system settings
158 */
159 private function prepare_cookies() {
160 global $CFG, $nomoodlecookie;
161
162 if (!defined('NO_MOODLE_COOKIES')) {
163 if (isset($nomoodlecookie)) {
164 // backwards compatibility only
165 define('NO_MOODLE_COOKIES', $nomoodlecookie);
166 unset($nomoodlecookie);
167 } else {
168 define('NO_MOODLE_COOKIES', false);
169 }
170 }
171
172 if (!isset($CFG->cookiesecure) or strpos($CFG->wwwroot, 'https://') !== 0) {
173 $CFG->cookiesecure = 0;
174 }
175
176 if (!isset($CFG->cookiehttponly)) {
177 $CFG->cookiehttponly = 0;
178 }
179
180 /// Set sessioncookie and sessioncookiepath variable if it isn't already
181 if (!isset($CFG->sessioncookie)) {
182 $CFG->sessioncookie = '';
183 }
e6e13284 184 if (!isset($CFG->sessioncookiedomain)) {
185 $CFG->sessioncookiedomain = '';
186 }
57f7b7ce 187 if (!isset($CFG->sessioncookiepath)) {
188 $CFG->sessioncookiepath = '/';
189 }
190
191 //discard session ID from POST, GET and globals to tighten security,
192 //this session fixation prevention can not be used in cookieless mode
193 if (empty($CFG->usesid)) {
194 unset(${'MoodleSession'.$CFG->sessioncookie});
195 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
196 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
197 }
198 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
199 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
200 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
201 }
202 if (!empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie]) && $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] == "deleted") {
203 unset($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie]);
204 }
205 }
206
207 /**
208 * Inits session storage.
209 */
210 private function init_session_storage() {
211 global $CFG;
212
213 /// Set up session handling
214 if(empty($CFG->respectsessionsettings)) {
215 if (true) { /// File-based sessions
216 // Some distros disable GC by setting probability to 0
217 // overriding the PHP default of 1
218 // (gc_probability is divided by gc_divisor, which defaults to 1000)
219 if (ini_get('session.gc_probability') == 0) {
220 ini_set('session.gc_probability', 1);
221 }
222
223 if (!empty($CFG->sessiontimeout)) {
224 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
225 }
226
227 if (!file_exists($CFG->dataroot .'/sessions')) {
228 make_upload_directory('sessions');
229 }
230 ini_set('session.save_path', $CFG->dataroot .'/sessions');
231
232 } else { /// Database sessions
233 // TODO: implement proper database session storage
234 }
235 }
236 }
237
238 /**
239 * Sets a moodle cookie with a weakly encrypted string
240 *
241 * @uses $CFG
242 * @uses DAYSECS
243 * @uses HOURSECS
244 * @param string $thing The string to encrypt and place in a cookie
245 */
246 public static function set_moodle_cookie($thing) {
247 global $CFG;
248
249 if ($thing == 'guest') { // Ignore guest account
250 return;
251 }
252
253 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
254
255 $days = 60;
256 $seconds = DAYSECS*$days;
257
258 // no need to set secure or http cookie only here - it is not secret
e6e13284 259 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
260 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
57f7b7ce 261 }
262
263 /**
264 * Gets a moodle cookie with a weakly encrypted string
265 *
266 * @uses $CFG
267 * @return string
268 */
269 public static function get_moodle_cookie() {
270 global $CFG;
271
272 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
273
274 if (empty($_COOKIE[$cookiename])) {
275 return '';
276 } else {
277 $thing = rc4decrypt($_COOKIE[$cookiename]);
278 return ($thing == 'guest') ? '': $thing; // Ignore guest account
279 }
280 }
281
282 /**
283 * Enable cookieless sessions by including $CFG->usesid=true;
284 * in config.php.
285 * Based on code from php manual by Richard at postamble.co.uk
286 * Attempts to use cookies if cookies not present then uses session ids attached to all urls and forms to pass session id from page to page.
287 * If site is open to google, google is given guest access as usual and there are no sessions. No session ids will be attached to urls for googlebot.
288 * This doesn't require trans_sid to be turned on but this is recommended for better performance
289 * you should put :
290 * session.use_trans_sid = 1
291 * in your php.ini file and make sure that you don't have a line like this in your php.ini
292 * session.use_only_cookies = 1
293 * @author Richard at postamble.co.uk and Jamie Pratt
294 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
295 */
296 /**
297 * You won't call this function directly. This function is used to process
298 * text buffered by php in an output buffer. All output is run through this function
299 * before it is ouput.
300 * @param string $buffer is the output sent from php
301 * @return string the output sent to the browser
302 */
303 public static function sid_ob_rewrite($buffer){
304 $replacements = array(
305 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
306 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
307
308 $buffer = preg_replace_callback($replacements, array('moodle_session', 'sid_rewrite_link_tag'), $buffer);
309 $buffer = preg_replace('/<form\s[^>]*>/i',
310 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
311
312 return $buffer;
313 }
314 /**
315 * You won't call this function directly. This function is used to process
316 * text buffered by php in an output buffer. All output is run through this function
317 * before it is ouput.
318 * This function only processes absolute urls, it is used when we decide that
319 * php is processing other urls itself but needs some help with internal absolute urls still.
320 * @param string $buffer is the output sent from php
321 * @return string the output sent to the browser
322 */
323 public static function sid_ob_rewrite_absolute($buffer){
324 $replacements = array(
325 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
326 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
327
328 $buffer = preg_replace_callback($replacements, array('moodle_session', 'sid_rewrite_link_tag'), $buffer);
329 $buffer = preg_replace('/<form\s[^>]*>/i',
330 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
331 return $buffer;
332 }
333
334 /**
335 * A function to process link, a and script tags found
336 * by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
337 */
338 public static function sid_rewrite_link_tag($matches){
339 $url = $matches[4];
340 $url = moodle_session::sid_process_url($url);
341 return $matches[1].$url.$matches[5];
342 }
343
344 /**
345 * You can call this function directly. This function is used to process
346 * urls to add a moodle session id to the url for internal links.
347 * @param string $url is a url
348 * @return string the processed url
349 */
350 public static function sid_process_url($url) {
351 global $CFG;
352
353 if ((preg_match('/^(http|https):/i', $url)) // absolute url
354 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
355 return $url; //don't attach sessid to non local urls
356 }
357 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
358 return $url; //don't attach sessid to anchors
359 }
360 if (strpos($url, session_name())!==FALSE) {
361 return $url; //don't attach sessid to url that already has one sessid
362 }
363 if (strpos($url, "?")===FALSE) {
364 $append = "?".strip_tags(session_name() . '=' . session_id());
365 } else {
366 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
367 }
368 //put sessid before any anchor
369 $p = strpos($url, "#");
370 if ($p!==FALSE){
371 $anch = substr($url, $p);
372 $url = substr($url, 0, $p).$append.$anch ;
373 } else {
374 $url .= $append ;
375 }
376 return $url;
377 }
378
379 /**
380 * Call this function before there has been any output to the browser to
381 * buffer output and add session ids to all internal links.
382 */
383 public static function sid_start_ob(){
384 global $CFG;
385 //don't attach sess id for bots
386
387 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
388 if (!empty($CFG->opentogoogle)) {
389 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
390 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
391 $CFG->usesid=false;
392 return;
393 }
394 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
395 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
396 $CFG->usesid=false;
397 return;
398 }
399 }
400 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
401 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
402 $CFG->usesid=false;
403 return;
404 }
405 }
406
407 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
408
409 if (ini_get('session.use_trans_sid') != 0) {
410 // use trans sid as its available
411 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
412 ob_start(array('moodle_session', 'sid_ob_rewrite_absolute'));
413 } else {
414 //rewrite all links ourselves
415 ob_start(array('moodle_session', 'sid_ob_rewrite'));
416 }
417 }
e6e13284 418}