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