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