reordering strings
[moodle.git] / lib / sessionlib.php
CommitLineData
b37eac91 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
57f7b7ce 17
542797b4 18/**
b37eac91 19 * @package moodlecore
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
542797b4 22 */
b37eac91 23
24/**
25 * Factory method returning moodle_session object.
26 * @return moodle_session
27 */
b7b64ff2 28function session_get_instance() {
5e9dd017 29 global $CFG, $DB;
0a2092a3 30
0ad6b20c 31 static $session = null;
32
33 if (is_null($session)) {
e8656bef 34 if (empty($CFG->sessiontimeout)) {
35 $CFG->sessiontimeout = 7200;
36 }
37
ad76d184 38 if (defined('SESSION_CUSTOM_CLASS')) {
e8656bef 39 // this is a hook for webservices, key based login, etc.
0a2092a3 40 if (defined('SESSION_CUSTOM_FILE')) {
41 require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
42 }
ad76d184 43 $session_class = SESSION_CUSTOM_CLASS;
0a2092a3 44 $session = new $session_class();
45
35d6a2a4 46 } else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
0a2092a3 47 // default recommended session type
48 $session = new database_session();
49
50 } else {
51 // legacy limited file based storage - some features and auth plugins will not work, sorry
52 $session = new legacy_file_session();
53 }
0ad6b20c 54 }
55
56 return $session;
57}
58
b37eac91 59/**
60 * @package moodlecore
61 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
62 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
63 */
0a2092a3 64interface moodle_session {
56949c17 65 /**
66 * Terminate current session
67 * @return void
68 */
69 public function terminate_current();
70
71 /**
72 * No more changes in session expected.
73 * Unblocks the sesions, other scripts may start executing in parallel.
74 * @return void
75 */
76 public function write_close();
0a2092a3 77}
78
57f7b7ce 79/**
80 * Class handling all session and cookies related stuff.
b37eac91 81 *
82 * @package moodlecore
83 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
84 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
57f7b7ce 85 */
0a2092a3 86abstract class session_stub implements moodle_session {
3b50631d 87 protected $justloggedout;
88
b7b64ff2 89 public function __construct() {
57f7b7ce 90 global $CFG;
57f7b7ce 91
0a2092a3 92 if (!defined('NO_MOODLE_COOKIES')) {
b9fb7103 93 if (empty($CFG->version) or $CFG->version < 2009011900) {
64d69e96 94 // no session before sessions table gets greated
95 define('NO_MOODLE_COOKIES', true);
96 } else if (CLI_SCRIPT) {
0a2092a3 97 // CLI scripts can not have session
98 define('NO_MOODLE_COOKIES', true);
99 } else {
100 define('NO_MOODLE_COOKIES', false);
101 }
57f7b7ce 102 }
103
0ad6b20c 104 if (NO_MOODLE_COOKIES) {
0a2092a3 105 // session not used at all
45871c08 106 $CFG->usesid = 0;
0a2092a3 107
0ad6b20c 108 $_SESSION = array();
109 $_SESSION['SESSION'] = new object();
0a2092a3 110 $_SESSION['USER'] = new object();
0ad6b20c 111
112 } else {
0a2092a3 113 $this->prepare_cookies();
114 $this->init_session_storage();
115
35d6a2a4 116 $newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
117
118 if (!empty($CFG->usesid) && $newsession) {
0a2092a3 119 sid_start_ob();
45871c08 120 } else {
121 $CFG->usesid = 0;
122 ini_set('session.use_trans_sid', '0');
0a2092a3 123 }
124
57f7b7ce 125 session_name('MoodleSession'.$CFG->sessioncookie);
e6e13284 126 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
64d69e96 127 session_start();
57f7b7ce 128 if (!isset($_SESSION['SESSION'])) {
129 $_SESSION['SESSION'] = new object();
3b50631d 130 if (!$newsession and !$this->justloggedout) {
35d6a2a4 131 $_SESSION['SESSION']->has_timed_out = true;
132 }
57f7b7ce 133 }
134 if (!isset($_SESSION['USER'])) {
135 $_SESSION['USER'] = new object();
136 }
57f7b7ce 137 }
57f7b7ce 138
b7b64ff2 139 $this->check_user_initialised();
9bda43e6 140
141 $this->check_security();
b7b64ff2 142 }
143
56949c17 144 /**
145 * Terminates active moodle session
146 */
147 public function terminate_current() {
148 global $CFG, $SESSION, $USER;
149
150 if (NO_MOODLE_COOKIES) {
151 return;
152 }
153
64d69e96 154 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
56949c17 155 $_SESSION = array();
35d6a2a4 156 $_SESSION['SESSION'] = new object();
157 $_SESSION['USER'] = new object();
158 $_SESSION['USER']->id = 0;
56949c17 159 if (isset($CFG->mnet_localhost_id)) {
35d6a2a4 160 $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
56949c17 161 }
35d6a2a4 162 $SESSION = $_SESSION['SESSION']; // this may not work properly
3b50631d 163 $USER = $_SESSION['USER']; // this may not work properly
35d6a2a4 164
56949c17 165 $file = null;
166 $line = null;
167 if (headers_sent($file, $line)) {
168 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
169 }
170
64d69e96 171 // now let's try to get a new session id and delete the old one
3b50631d 172 $this->justloggedout = true;
173 session_regenerate_id(true);
174 $this->justloggedout = false;
175
64d69e96 176 // write the new session
35d6a2a4 177 session_write_close();
56949c17 178 }
179
180 /**
181 * No more changes in session expected.
182 * Unblocks the sesions, other scripts may start executing in parallel.
183 * @return void
184 */
185 public function write_close() {
186 if (NO_MOODLE_COOKIES) {
187 return;
188 }
189
190 session_write_close();
191 }
192
b7b64ff2 193 /**
dd9e22f8 194 * Initialise $USER object, handles google access
195 * and sets up not logged in user properly.
b7b64ff2 196 *
197 * @return void
198 */
199 protected function check_user_initialised() {
200 if (isset($_SESSION['USER']->id)) {
201 // already set up $USER
202 return;
203 }
204
205 $user = null;
206
207 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
208 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
209 // allow web spiders in as guest users
210 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
211 $user = guest_user();
212 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
213 $user = guest_user();
214 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) { // Yahoo
215 $user = guest_user();
216 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) { // Zoomspider
217 $user = guest_user();
218 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) { // MSN Search
219 $user = guest_user();
220 }
221 }
1b813f5c 222 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
b7b64ff2 223 // automaticaly log in users coming from search engine results
224 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
225 $user = guest_user();
226 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
227 $user = guest_user();
228 }
229 }
230 }
231
232 if (!$user) {
233 $user = new object();
234 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
0ad6b20c 235 if (isset($CFG->mnet_localhost_id)) {
b7b64ff2 236 $user->mnethostid = $CFG->mnet_localhost_id;
0a2092a3 237 } else {
238 $user->mnethostid = 1;
57f7b7ce 239 }
240 }
b7b64ff2 241 session_set_user($user);
57f7b7ce 242 }
243
9bda43e6 244 /**
245 * Does various session security checks
246 * @global void
247 */
93f66983 248 protected function check_security() {
249 global $CFG;
250
0a2092a3 251 if (NO_MOODLE_COOKIES) {
252 return;
253 }
254
9bda43e6 255 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
256 /// Make sure current IP matches the one for this session
93f66983 257 $remoteaddr = getremoteaddr();
258
259 if (empty($_SESSION['USER']->sessionip)) {
260 $_SESSION['USER']->sessionip = $remoteaddr;
261 }
262
263 if ($_SESSION['USER']->sessionip != $remoteaddr) {
9bda43e6 264 // this is a security feature - terminate the session in case of any doubt
56949c17 265 $this->terminate_current();
9bda43e6 266 print_error('sessionipnomatch2', 'error');
93f66983 267 }
268 }
93f66983 269 }
270
57f7b7ce 271 /**
272 * Prepare cookies and varions system settings
273 */
b7b64ff2 274 protected function prepare_cookies() {
0a2092a3 275 global $CFG;
57f7b7ce 276
11e7b506 277 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
57f7b7ce 278 $CFG->cookiesecure = 0;
279 }
280
281 if (!isset($CFG->cookiehttponly)) {
282 $CFG->cookiehttponly = 0;
283 }
284
285 /// Set sessioncookie and sessioncookiepath variable if it isn't already
286 if (!isset($CFG->sessioncookie)) {
287 $CFG->sessioncookie = '';
288 }
e6e13284 289 if (!isset($CFG->sessioncookiedomain)) {
290 $CFG->sessioncookiedomain = '';
291 }
57f7b7ce 292 if (!isset($CFG->sessioncookiepath)) {
293 $CFG->sessioncookiepath = '/';
294 }
295
296 //discard session ID from POST, GET and globals to tighten security,
297 //this session fixation prevention can not be used in cookieless mode
298 if (empty($CFG->usesid)) {
299 unset(${'MoodleSession'.$CFG->sessioncookie});
300 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
301 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
b7b64ff2 302 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
57f7b7ce 303 }
304 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
305 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
306 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
307 }
57f7b7ce 308 }
309
310 /**
311 * Inits session storage.
312 */
f61a032a 313 protected abstract function init_session_storage();
f61a032a 314}
315
316/**
317 * Legacy moodle sessions stored in files, not recommended any more.
b37eac91 318 *
319 * @package moodlecore
320 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
321 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f61a032a 322 */
0a2092a3 323class legacy_file_session extends session_stub {
b7b64ff2 324 protected function init_session_storage() {
57f7b7ce 325 global $CFG;
326
0a2092a3 327 ini_set('session.save_handler', 'files');
328
f61a032a 329 // Some distros disable GC by setting probability to 0
330 // overriding the PHP default of 1
331 // (gc_probability is divided by gc_divisor, which defaults to 1000)
332 if (ini_get('session.gc_probability') == 0) {
333 ini_set('session.gc_probability', 1);
334 }
57f7b7ce 335
3b1a9849 336 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
57f7b7ce 337
f61a032a 338 if (!file_exists($CFG->dataroot .'/sessions')) {
339 make_upload_directory('sessions');
340 }
341 if (!is_writable($CFG->dataroot .'/sessions/')) {
342 print_error('sessionnotwritable', 'error');
57f7b7ce 343 }
2d801928 344 // Need to disable debugging since disk_free_space()
345 // will fail on very large partitions (see MDL-19222)
346 $freespace = @disk_free_space($CFG->dataroot.'/sessions');
347 if (!($freespace > 2048) and $freespace !== false) {
55059253 348 print_error('sessiondiskfull', 'error');
349 }
f61a032a 350 ini_set('session.save_path', $CFG->dataroot .'/sessions');
351 }
352}
353
354/**
355 * Recommended moodle session storage.
b37eac91 356 *
357 * @package moodlecore
358 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
359 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f61a032a 360 */
0a2092a3 361class database_session extends session_stub {
362 protected $record = null;
363 protected $database = null;
364
dd9e22f8 365 public function __construct() {
366 global $DB;
367 $this->database = $DB;
368 parent::__construct();
b9fb7103 369
370 if (!empty($this->record->state)) {
371 // something is very wrong
372 session_kill($this->record->sid);
373
374 if ($this->record->state == 9) {
375 print_error('dbsessionmysqlpacketsize', 'error');
376 }
377 }
dd9e22f8 378 }
379
f61a032a 380 protected function init_session_storage() {
381 global $CFG;
382
dd9e22f8 383 // gc only from CRON - individual user timeouts now checked during each access
384 ini_set('session.gc_probability', 0);
0a2092a3 385
ef159e5f 386 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
0a2092a3 387
388 $result = session_set_save_handler(array($this, 'handler_open'),
389 array($this, 'handler_close'),
390 array($this, 'handler_read'),
391 array($this, 'handler_write'),
392 array($this, 'handler_destroy'),
393 array($this, 'handler_gc'));
394 if (!$result) {
eee3bd3f 395 print_error('dbsessionhandlerproblem', 'error');
0a2092a3 396 }
397 }
398
dd9e22f8 399 public function handler_open($save_path, $session_name) {
0a2092a3 400 return true;
401 }
402
403 public function handler_close() {
3b50631d 404 if (isset($this->record->id)) {
405 $this->database->release_session_lock($this->record->id);
406 }
0a2092a3 407 $this->record = null;
408 return true;
409 }
410
411 public function handler_read($sid) {
412 global $CFG;
413
0a2092a3 414 if ($this->record and $this->record->sid != $sid) {
1c13ff23 415 error_log('Weird error reading database session - mismatched sid');
0a2092a3 416 return '';
417 }
418
419 try {
3b1a9849 420 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
421 $this->database->get_session_lock($record->id);
dd9e22f8 422
3b1a9849 423 } else {
0a2092a3 424 $record = new object();
425 $record->state = 0;
426 $record->sid = $sid;
427 $record->sessdata = null;
0a2092a3 428 $record->userid = 0;
429 $record->timecreated = $record->timemodified = time();
430 $record->firstip = $record->lastip = getremoteaddr();
3b50631d 431 $record->id = $this->database->insert_record_raw('sessions', $record);
0a2092a3 432
3b1a9849 433 $this->database->get_session_lock($record->id);
0a2092a3 434 }
435 } catch (dml_exception $ex) {
64d69e96 436 error_log('Can not read or insert database sessions');
0a2092a3 437 return '';
438 }
439
3b1a9849 440 // verify timeout
441 if ($record->timemodified + $CFG->sessiontimeout < time()) {
dd9e22f8 442 $ignoretimeout = false;
88fdd846 443 if (!empty($record->userid)) { // skips not logged in
444 if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
445 if ($user->username !== 'guest') {
446 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
447 foreach($authsequence as $authname) {
448 $authplugin = get_auth_plugin($authname);
e8656bef 449 if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
88fdd846 450 $ignoretimeout = true;
451 break;
452 }
453 }
454 }
dd9e22f8 455 }
456 }
457 if ($ignoretimeout) {
458 //refresh session
459 $record->timemodified = time();
460 try {
461 $this->database->update_record('sessions', $record);
462 } catch (dml_exception $ex) {
463 error_log('Can not refresh database session');
464 return '';
465 }
466 } else {
467 //time out session
468 $record->state = 0;
469 $record->sessdata = null;
dd9e22f8 470 $record->userid = 0;
471 $record->timecreated = $record->timemodified = time();
472 $record->firstip = $record->lastip = getremoteaddr();
473 try {
474 $this->database->update_record('sessions', $record);
475 } catch (dml_exception $ex) {
476 error_log('Can not time out database session');
477 return '';
478 }
eee3bd3f 479 }
eee3bd3f 480 }
481
b9fb7103 482 $data = is_null($record->sessdata) ? '' : base64_decode($record->sessdata);
3b1a9849 483
0a2092a3 484 unset($record->sessdata); // conserve memory
485 $this->record = $record;
486
487 return $data;
488 }
489
490 public function handler_write($sid, $session_data) {
491 global $USER;
492
59e0ce0a
PS
493 // TODO: MDL-20625 we need to rollabck all active transactions and log error if any open needed
494
b9fb7103 495 $userid = 0;
496 if (!empty($USER->realuser)) {
497 $userid = $USER->realuser;
498 } else if (!empty($USER->id)) {
499 $userid = $USER->id;
500 }
501
502 if (isset($this->record->id)) {
503 $record->state = 0;
504 $record->sid = $sid; // might be regenerating sid
505 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
506 $this->record->userid = $userid;
507 $this->record->timemodified = time();
508 $this->record->lastip = getremoteaddr();
0a2092a3 509
3a465d1d 510 // TODO: verify session changed before doing update,
511 // also make sure the timemodified field is changed only overy 10s if nothing else changes MDL-20462
7f79aaea 512
b9fb7103 513 try {
3b50631d 514 $this->database->update_record_raw('sessions', $this->record);
b9fb7103 515 } catch (dml_exception $ex) {
516 if ($this->database->get_dbfamily() === 'mysql') {
517 try {
518 $this->database->set_field('sessions', 'state', 9, array('id'=>$this->record->id));
519 } catch (Exception $ignored) {
0a2092a3 520
b9fb7103 521 }
1c13ff23 522 error_log('Can not write database session - please verify max_allowed_packet is at least 4M!');
b9fb7103 523 } else {
1c13ff23 524 error_log('Can not write database session');
b9fb7103 525 }
526 }
eee3bd3f 527
b9fb7103 528 } else {
529 // session already destroyed
530 $record = new object();
531 $record->state = 0;
532 $record->sid = $sid;
533 $record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
534 $record->userid = $userid;
535 $record->timecreated = $record->timemodified = time();
536 $record->firstip = $record->lastip = getremoteaddr();
537 $record->id = $this->database->insert_record_raw('sessions', $record);
538 $this->record = $record;
539
540 try {
3b50631d 541 $this->database->get_session_lock($this->record->id);
b9fb7103 542 } catch (dml_exception $ex) {
1c13ff23 543 error_log('Can not write new database session');
3b50631d 544 }
0a2092a3 545 }
17d93489 546
0a2092a3 547 return true;
548 }
549
550 public function handler_destroy($sid) {
3b50631d 551 session_kill($sid);
7f79aaea 552
3b50631d 553 if (isset($this->record->id) and $this->record->sid === $sid) {
554 $this->database->release_session_lock($this->record->id);
555 $this->record = null;
0a2092a3 556 }
557
558 return true;
559 }
560
3b1a9849 561 public function handler_gc($ignored_maxlifetime) {
64d69e96 562 session_gc();
0a2092a3 563 return true;
57f7b7ce 564 }
e8656bef 565}
566
64d69e96 567/**
568 * returns true if legacy session used.
569 * @return bool true if legacy(==file) based session used
570 */
571function session_is_legacy() {
572 global $CFG, $DB;
573 return ((isset($CFG->dbsessions) and !$CFG->dbsessions) or !$DB->session_lock_supported());
574}
575
e8656bef 576/**
577 * Terminates all sessions, auth hooks are not executed.
578 * Useful in ugrade scripts.
579 */
580function session_kill_all() {
581 global $CFG, $DB;
582
64d69e96 583 // always check db table - custom session classes use sessions table
e8656bef 584 try {
e8656bef 585 $DB->delete_records('sessions');
586 } catch (dml_exception $ignored) {
64d69e96 587 // do not show any warnings - might be during upgrade/installation
588 }
589
590 if (session_is_legacy()) {
591 $sessiondir = "$CFG->dataroot/sessions";
592 if (is_dir($sessiondir)) {
593 foreach (glob("$sessiondir/sess_*") as $filename) {
594 @unlink($filename);
595 }
596 }
597 }
598}
599
600/**
601 * Mark session as accessed, prevents timeouts.
602 * @param string $sid
603 */
604function session_touch($sid) {
605 global $CFG, $DB;
606
607 // always check db table - custom session classes use sessions table
608 try {
609 $sql = "UPDATE {sessions} SET timemodified=? WHERE sid=?";
610 $params = array(time(), $sid);
611 $DB->execute($sql, $params);
612 } catch (dml_exception $ignored) {
613 // do not show any warnings - might be during upgrade/installation
e8656bef 614 }
0a2092a3 615
64d69e96 616 if (session_is_legacy()) {
617 $sid = clean_param($sid, PARAM_FILE);
618 $sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
619 if (file_exists($sessionfile)) {
620 // if the file is locked it means that it will be updated anyway
621 @touch($sessionfile);
622 }
e8656bef 623 }
624}
625
626/**
627 * Terminates one sessions, auth hooks are not executed.
628 *
629 * @param string $sid session id
630 */
631function session_kill($sid) {
632 global $CFG, $DB;
633
64d69e96 634 // always check db table - custom session classes use sessions table
e8656bef 635 try {
64d69e96 636 $DB->delete_records('sessions', array('sid'=>$sid));
e8656bef 637 } catch (dml_exception $ignored) {
64d69e96 638 // do not show any warnings - might be during upgrade/installation
e8656bef 639 }
640
64d69e96 641 if (session_is_legacy()) {
642 $sid = clean_param($sid, PARAM_FILE);
643 $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
644 if (file_exists($sessionfile)) {
645 @unlink($sessionfile);
646 }
e8656bef 647 }
648}
649
650/**
651 * Terminates all sessions of one user, auth hooks are not executed.
652 * NOTE: This can not work for file based sessions!
653 *
654 * @param int $userid user id
655 */
656function session_kill_user($userid) {
657 global $CFG, $DB;
658
64d69e96 659 // always check db table - custom session classes use sessions table
e8656bef 660 try {
64d69e96 661 $DB->delete_records('sessions', array('userid'=>$userid));
e8656bef 662 } catch (dml_exception $ignored) {
64d69e96 663 // do not show any warnings - might be during upgrade/installation
664 }
665
666 if (session_is_legacy()) {
667 // log error?
e8656bef 668 }
669}
670
671/**
672 * Session garbage collection
673 * - verify timeout for all users
674 * - kill sessions of all deleted users
675 * - kill sessions of users with disabled plugins or 'nologin' plugin
676 *
677 * NOTE: this can not work when legacy file sessions used!
678 */
679function session_gc() {
680 global $CFG, $DB;
681
682 $maxlifetime = $CFG->sessiontimeout;
683
e8656bef 684 try {
685 /// kill all sessions of deleted users
686 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
687
688 /// kill sessions of users with disabled plugins
689 $auth_sequence = get_enabled_auth_plugins(true);
690 $auth_sequence = array_flip($auth_sequence);
691 unset($auth_sequence['nologin']); // no login allowed
692 $auth_sequence = array_flip($auth_sequence);
693 $notplugins = null;
694 list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
695 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
696
697 /// now get a list of time-out candidates
698 $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
699 FROM {user} u
700 JOIN {sessions} s ON s.userid = u.id
701 WHERE s.timemodified + ? < ? AND u.username <> 'guest'";
702 $params = array($maxlifetime, time());
703
704 $authplugins = array();
705 foreach($auth_sequence as $authname) {
706 $authplugins[$authname] = get_auth_plugin($authname);
707 }
708 $rs = $DB->get_recordset_sql($sql, $params);
709 foreach ($rs as $user) {
710 foreach ($authplugins as $authplugin) {
711 if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
712 continue;
713 }
714 }
715 $DB->delete_records('sessions', array('sid'=>$user->sid));
716 }
717 $rs->close();
718 } catch (dml_exception $ex) {
719 error_log('Error gc-ing sessions');
720 }
0ad6b20c 721}
57f7b7ce 722
0ad6b20c 723/**
724 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
725 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
726 * sesskey string if $USER exists, or boolean false if not.
727 *
728 * @uses $USER
729 * @return string
730 */
731function sesskey() {
732 global $USER;
57f7b7ce 733
0ad6b20c 734 if (empty($USER->sesskey)) {
735 $USER->sesskey = random_string(10);
736 }
57f7b7ce 737
0ad6b20c 738 return $USER->sesskey;
739}
57f7b7ce 740
57f7b7ce 741
0ad6b20c 742/**
a79ef03f
TH
743 * Check the sesskey and return true of false for whether it is valid.
744 * (You might like to imagine this function is called sesskey_is_valid().)
0ad6b20c 745 *
a79ef03f
TH
746 * Every script that lets the user perform a significant action (that is,
747 * changes data in the database) should check the sesskey before doing the action.
748 * Depending on your code flow, you may want to use the {@link require_sesskey()}
749 * helper function.
750 *
751 * @param string $sesskey The sesskey value to check (optional). Normally leave this blank
752 * and this function will do required_param('sesskey', ...).
753 * @return bool whether the sesskey sent in the request matches the one stored in the session.
0ad6b20c 754 */
755function confirm_sesskey($sesskey=NULL) {
756 global $USER;
57f7b7ce 757
eb85959b 758 if (!empty($USER->ignoresesskey)) {
0ad6b20c 759 return true;
760 }
57f7b7ce 761
0ad6b20c 762 if (empty($sesskey)) {
763 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
57f7b7ce 764 }
765
eb85959b 766 return (sesskey() === $sesskey);
0ad6b20c 767}
768
a79ef03f
TH
769/**
770 * Check the session key using {@link confirm_sesskey()},
771 * and cause a fatal error if it does not match.
772 */
773function require_sesskey() {
774 if (!confirm_sesskey()) {
775 print_error('invalidsesskey');
776 }
777}
778
0ad6b20c 779/**
780 * Sets a moodle cookie with a weakly encrypted string
781 *
782 * @uses $CFG
783 * @uses DAYSECS
784 * @uses HOURSECS
785 * @param string $thing The string to encrypt and place in a cookie
786 */
787function set_moodle_cookie($thing) {
788 global $CFG;
789
0a2092a3 790 if (NO_MOODLE_COOKIES) {
791 return;
792 }
793
0ad6b20c 794 if ($thing == 'guest') { // Ignore guest account
795 return;
57f7b7ce 796 }
797
0ad6b20c 798 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
799
800 $days = 60;
801 $seconds = DAYSECS*$days;
802
a91557ae 803 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
804 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
0ad6b20c 805}
806
807/**
808 * Gets a moodle cookie with a weakly encrypted string
809 *
810 * @uses $CFG
811 * @return string
812 */
813function get_moodle_cookie() {
814 global $CFG;
815
0a2092a3 816 if (NO_MOODLE_COOKIES) {
817 return '';
818 }
819
0ad6b20c 820 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
821
822 if (empty($_COOKIE[$cookiename])) {
823 return '';
824 } else {
825 $thing = rc4decrypt($_COOKIE[$cookiename]);
826 return ($thing == 'guest') ? '': $thing; // Ignore guest account
57f7b7ce 827 }
0ad6b20c 828}
57f7b7ce 829
e8656bef 830
b7b64ff2 831/**
832 * Setup $USER object - called during login, loginas, etc.
833 * Preloads capabilities and checks enrolment plugins
834 *
835 * @param object $user full user record object
836 * @return void
837 */
838function session_set_user($user) {
839 $_SESSION['USER'] = $user;
dd9e22f8 840 unset($_SESSION['USER']->description); // conserve memory
841 if (!isset($_SESSION['USER']->access)) {
842 // check enrolments and load caps only once
843 check_enrolment_plugins($_SESSION['USER']);
844 load_all_capabilities();
845 }
eb85959b 846 sesskey(); // init session key
b7b64ff2 847}
848
542797b4 849/**
850 * Is current $USER logged-in-as somebody else?
851 * @return bool
852 */
b7b64ff2 853function session_is_loggedinas() {
85f6b737 854 return !empty($_SESSION['USER']->realuser);
542797b4 855}
856
6132768e 857/**
858 * Returns the $USER object ignoring current login-as session
859 * @return object user object
860 */
b7b64ff2 861function session_get_realuser() {
862 if (session_is_loggedinas()) {
6132768e 863 return $_SESSION['REALUSER'];
864 } else {
865 return $_SESSION['USER'];
866 }
867}
868
542797b4 869/**
870 * Login as another user - no security checks here.
871 * @param int $userid
872 * @param object $context
873 * @return void
874 */
8d1964c4 875function session_loginas($userid, $context) {
b7b64ff2 876 if (session_is_loggedinas()) {
8d1964c4 877 return;
878 }
879
85f6b737 880 // switch to fresh new $SESSION
881 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
6132768e 882 $_SESSION['SESSION'] = new object();
8d1964c4 883
85f6b737 884 /// Create the new $USER object with all details and reload needed capabilitites
885 $_SESSION['REALUSER'] = $_SESSION['USER'];
b7b64ff2 886 $user = get_complete_user_data('id', $userid);
887 $user->realuser = $_SESSION['REALUSER']->id;
888 $user->loginascontext = $context;
889 session_set_user($user);
8d1964c4 890}
891
542797b4 892/**
893 * Terminate login-as session
894 * @return void
895 */
8d1964c4 896function session_unloginas() {
b7b64ff2 897 if (!session_is_loggedinas()) {
8d1964c4 898 return;
899 }
900
6132768e 901 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
902 unset($_SESSION['REALSESSION']);
8d1964c4 903
6132768e 904 $_SESSION['USER'] = $_SESSION['REALUSER'];
905 unset($_SESSION['REALUSER']);
8d1964c4 906}
907
e8b7114d 908/**
909 * Sets up current user and course enviroment (lang, etc.) in cron.
910 * Do not use outside of cron script!
911 *
912 * @param object $user full user object, null means default cron user (admin)
913 * @param $course full course record, null means $SITE
914 * @return void
915 */
916function cron_setup_user($user=null, $course=null) {
c13a5e71 917 global $CFG, $SITE, $PAGE;
e8b7114d 918
919 static $cronuser = null;
920 static $cronsession = null;
921
922 if (empty($cronuser)) {
923 /// ignore admins timezone, language and locale - use site deafult instead!
924 $cronuser = get_admin();
925 $cronuser->timezone = $CFG->timezone;
dd9e22f8 926 $cronuser->lang = '';
927 $cronuser->theme = '';
928 unset($cronuser->description);
e8b7114d 929
930 $cronsession = array();
931 }
932
933 if (!$user) {
934 // cached default cron user (==modified admin for now)
935 session_set_user($cronuser);
936 $_SESSION['SESSION'] = $cronsession;
937
938 } else {
939 // emulate real user session - needed for caps in cron
940 if ($_SESSION['USER']->id != $user->id) {
941 session_set_user($user);
942 $_SESSION['SESSION'] = array();
943 }
944 }
945
43b152f6 946 // TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
947 // Temporary hack so that cron does not give fatal errors.
948 $PAGE = new moodle_page();
e8b7114d 949 if ($course) {
c13a5e71 950 $PAGE->set_course($course);
e8b7114d 951 } else {
c13a5e71 952 $PAGE->set_course($SITE);
e8b7114d 953 }
954
955 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
956
957}
958
0ad6b20c 959/**
960* Enable cookieless sessions by including $CFG->usesid=true;
961* in config.php.
962* Based on code from php manual by Richard at postamble.co.uk
963* 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.
964* 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.
965* This doesn't require trans_sid to be turned on but this is recommended for better performance
966* you should put :
967* session.use_trans_sid = 1
968* in your php.ini file and make sure that you don't have a line like this in your php.ini
969* session.use_only_cookies = 1
970* @author Richard at postamble.co.uk and Jamie Pratt
971* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
972*/
973/**
974* You won't call this function directly. This function is used to process
975* text buffered by php in an output buffer. All output is run through this function
976* before it is ouput.
977* @param string $buffer is the output sent from php
978* @return string the output sent to the browser
979*/
980function sid_ob_rewrite($buffer){
981 $replacements = array(
982 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
983 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
984
985 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
986 $buffer = preg_replace('/<form\s[^>]*>/i',
987 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
988
989 return $buffer;
990}
991/**
992* You won't call this function directly. This function is used to process
993* text buffered by php in an output buffer. All output is run through this function
994* before it is ouput.
995* This function only processes absolute urls, it is used when we decide that
996* php is processing other urls itself but needs some help with internal absolute urls still.
997* @param string $buffer is the output sent from php
998* @return string the output sent to the browser
999*/
1000function sid_ob_rewrite_absolute($buffer){
1001 $replacements = array(
1002 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
1003 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
1004
1005 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
1006 $buffer = preg_replace('/<form\s[^>]*>/i',
1007 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
1008 return $buffer;
1009}
57f7b7ce 1010
0ad6b20c 1011/**
1012* A function to process link, a and script tags found
1013* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
1014*/
1015function sid_rewrite_link_tag($matches){
1016 $url = $matches[4];
1017 $url = sid_process_url($url);
1018 return $matches[1].$url.$matches[5];
1019}
1020
1021/**
1022* You can call this function directly. This function is used to process
1023* urls to add a moodle session id to the url for internal links.
1024* @param string $url is a url
1025* @return string the processed url
1026*/
1027function sid_process_url($url) {
1028 global $CFG;
1029
1030 if ((preg_match('/^(http|https):/i', $url)) // absolute url
1031 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
1032 return $url; //don't attach sessid to non local urls
1033 }
1034 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
1035 return $url; //don't attach sessid to anchors
1036 }
1037 if (strpos($url, session_name())!==FALSE) {
1038 return $url; //don't attach sessid to url that already has one sessid
1039 }
1040 if (strpos($url, "?")===FALSE) {
1041 $append = "?".strip_tags(session_name() . '=' . session_id());
1042 } else {
1043 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 1044 }
0ad6b20c 1045 //put sessid before any anchor
1046 $p = strpos($url, "#");
1047 if ($p!==FALSE){
1048 $anch = substr($url, $p);
1049 $url = substr($url, 0, $p).$append.$anch ;
1050 } else {
1051 $url .= $append ;
1052 }
1053 return $url;
1054}
57f7b7ce 1055
0ad6b20c 1056/**
1057* Call this function before there has been any output to the browser to
1058* buffer output and add session ids to all internal links.
1059*/
1060function sid_start_ob(){
1061 global $CFG;
1062 //don't attach sess id for bots
1063
1064 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
1065 if (!empty($CFG->opentogoogle)) {
1066 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
1067 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1068 $CFG->usesid=false;
1069 return;
57f7b7ce 1070 }
0ad6b20c 1071 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 1072 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1073 $CFG->usesid=false;
1074 return;
1075 }
1076 }
0ad6b20c 1077 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
1078 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1079 $CFG->usesid=false;
1080 return;
1081 }
1082 }
57f7b7ce 1083
0ad6b20c 1084 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 1085
0ad6b20c 1086 if (ini_get('session.use_trans_sid') != 0) {
1087 // use trans sid as its available
1088 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
1089 ob_start('sid_ob_rewrite_absolute');
1090 } else {
1091 //rewrite all links ourselves
1092 ob_start('sid_ob_rewrite');
57f7b7ce 1093 }
e6e13284 1094}
0ad6b20c 1095