From: skodak Date: Sun, 22 Jun 2008 11:56:01 +0000 (+0000) Subject: MDL-14992 ugly workaround for http://bugs.php.net/bug.php?id=39449 X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=57f7b7cebef7d449ff3b9c7b76d03778426e1eb4;p=moodle.git MDL-14992 ugly workaround for http://bugs.php.net/bug.php?id=39449 --- diff --git a/lib/sessionlib.php b/lib/sessionlib.php new file mode 100644 index 0000000000..887a33912e --- /dev/null +++ b/lib/sessionlib.php @@ -0,0 +1,415 @@ +prepare_cookies(); + $this->init_session_storage(); + + if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) { + $this->sid_start_ob(); + } + + if (!NO_MOODLE_COOKIES) { + session_name('MoodleSession'.$CFG->sessioncookie); + session_set_cookie_params(0, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly); + @session_start(); + if (!isset($_SESSION['SESSION'])) { + $_SESSION['SESSION'] = new object(); + $_SESSION['SESSION']->session_test = random_string(10); + if (!empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie])) { + $_SESSION['SESSION']->has_timed_out = true; + } + setcookie('MoodleSessionTest'.$CFG->sessioncookie, $_SESSION['SESSION']->session_test, 0, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly); + $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] = $_SESSION['SESSION']->session_test; + } + if (!isset($_SESSION['USER'])) { + $_SESSION['USER'] = new object(); + } + + if (!isset($_SESSION['USER']->id)) { + $_SESSION['USER']->id = 0; // to enable proper function of $CFG->notloggedinroleid hack + if (isset($CFG->mnet_localhost_id)) { + $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id; + } + } + + $this->session = null; + + } else { + $this->session = new object(); + } + } + + /** + * Verify session, this detects problems with "switched" sessions + * or multiple different wwwroot used at the same time. + */ + public function session_verify() { + global $CFG; + + /// disable checks when working in cookieless mode + if (empty($CFG->usesid) || !empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) { + if ($this->session != NULL) { + if (empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie])) { + $this->report_session_error(); + } else if (isset($this->session->session_test) && $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] != $this->session->session_test) { + $this->report_session_error(); + } + } + } + } + + /** + * Report serious problem detected in suer session + */ + function report_session_error() { + global $CFG, $FULLME; + + if (empty($CFG->lang)) { + $CFG->lang = "en"; + } + // Set up default theme and locale + theme_setup(); + moodle_setlocale(); + + //clear session cookies + setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly); + setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly); + + //increment database error counters + if (isset($CFG->session_error_counter)) { + set_config('session_error_counter', 1 + $CFG->session_error_counter); + } else { + set_config('session_error_counter', 1); + } + redirect($FULLME, get_string('sessionerroruser2', 'error'), 5); + } + + /** + * Terminates active moodle session + */ + public function terminate() { + global $CFG, $SESSION, $USER; + + // Initialize variable to pass-by-reference to headers_sent(&$file, &$line) + $file = null; + $line = null; + if (headers_sent($file, $line)) { + error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__); + error_log('Headers were already sent in file: '.$file.' on line '.$line); + } else { + setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly); + } + + $this->session = new object(); + $_SESSION = array(); + + $USER = new object(); + $USER->id = 0; + if (isset($CFG->mnet_localhost_id)) { + $USER->mnethostid = $CFG->mnet_localhost_id; + } + + @session_write_close(); + } + + + public function __set($name, $value) { + if (!is_null($this->session)) { + $this->session->{$name} = $value; + } else { + $_SESSION['SESSION']->{$name} = $value; + } + } + + public function &__get($name) { // this is a weird hack for this stupid bug http://bugs.php.net/bug.php?id=39449 + if (!is_null($this->session)) { + return $this->session->{$name}; + } else { + return $_SESSION['SESSION']->{$name}; + } + } + + public function __isset($name) { + if (!is_null($this->session)) { + return isset($this->session->{$name}); + } else { + return isset($_SESSION['SESSION']->{$name}); + } + } + + public function __unset($name) { + if (!is_null($this->session)) { + unset($this->session->{$name}); + } else { + unset($_SESSION['SESSION']->{$name}); + } + } + + /** + * Prepare cookies and varions system settings + */ + private function prepare_cookies() { + global $CFG, $nomoodlecookie; + + if (!defined('NO_MOODLE_COOKIES')) { + if (isset($nomoodlecookie)) { + // backwards compatibility only + define('NO_MOODLE_COOKIES', $nomoodlecookie); + unset($nomoodlecookie); + } else { + define('NO_MOODLE_COOKIES', false); + } + } + + if (!isset($CFG->cookiesecure) or strpos($CFG->wwwroot, 'https://') !== 0) { + $CFG->cookiesecure = 0; + } + + if (!isset($CFG->cookiehttponly)) { + $CFG->cookiehttponly = 0; + } + + /// Set sessioncookie and sessioncookiepath variable if it isn't already + if (!isset($CFG->sessioncookie)) { + $CFG->sessioncookie = ''; + } + if (!isset($CFG->sessioncookiepath)) { + $CFG->sessioncookiepath = '/'; + } + + //discard session ID from POST, GET and globals to tighten security, + //this session fixation prevention can not be used in cookieless mode + if (empty($CFG->usesid)) { + unset(${'MoodleSession'.$CFG->sessioncookie}); + unset($_GET['MoodleSession'.$CFG->sessioncookie]); + unset($_POST['MoodleSession'.$CFG->sessioncookie]); + } + //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now + if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") { + unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]); + } + if (!empty($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie]) && $_COOKIE['MoodleSessionTest'.$CFG->sessioncookie] == "deleted") { + unset($_COOKIE['MoodleSessionTest'.$CFG->sessioncookie]); + } + } + + /** + * Inits session storage. + */ + private function init_session_storage() { + global $CFG; + + /// Set up session handling + if(empty($CFG->respectsessionsettings)) { + if (true) { /// File-based sessions + // Some distros disable GC by setting probability to 0 + // overriding the PHP default of 1 + // (gc_probability is divided by gc_divisor, which defaults to 1000) + if (ini_get('session.gc_probability') == 0) { + ini_set('session.gc_probability', 1); + } + + if (!empty($CFG->sessiontimeout)) { + ini_set('session.gc_maxlifetime', $CFG->sessiontimeout); + } + + if (!file_exists($CFG->dataroot .'/sessions')) { + make_upload_directory('sessions'); + } + ini_set('session.save_path', $CFG->dataroot .'/sessions'); + + } else { /// Database sessions + // TODO: implement proper database session storage + } + } + } + + /** + * Sets a moodle cookie with a weakly encrypted string + * + * @uses $CFG + * @uses DAYSECS + * @uses HOURSECS + * @param string $thing The string to encrypt and place in a cookie + */ + public static function set_moodle_cookie($thing) { + global $CFG; + + if ($thing == 'guest') { // Ignore guest account + return; + } + + $cookiename = 'MOODLEID_'.$CFG->sessioncookie; + + $days = 60; + $seconds = DAYSECS*$days; + + // no need to set secure or http cookie only here - it is not secret + setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath); + setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath); + } + + /** + * Gets a moodle cookie with a weakly encrypted string + * + * @uses $CFG + * @return string + */ + public static function get_moodle_cookie() { + global $CFG; + + $cookiename = 'MOODLEID_'.$CFG->sessioncookie; + + if (empty($_COOKIE[$cookiename])) { + return ''; + } else { + $thing = rc4decrypt($_COOKIE[$cookiename]); + return ($thing == 'guest') ? '': $thing; // Ignore guest account + } + } + + /** + * Enable cookieless sessions by including $CFG->usesid=true; + * in config.php. + * Based on code from php manual by Richard at postamble.co.uk + * 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. + * 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. + * This doesn't require trans_sid to be turned on but this is recommended for better performance + * you should put : + * session.use_trans_sid = 1 + * in your php.ini file and make sure that you don't have a line like this in your php.ini + * session.use_only_cookies = 1 + * @author Richard at postamble.co.uk and Jamie Pratt + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + /** + * You won't call this function directly. This function is used to process + * text buffered by php in an output buffer. All output is run through this function + * before it is ouput. + * @param string $buffer is the output sent from php + * @return string the output sent to the browser + */ + public static function sid_ob_rewrite($buffer){ + $replacements = array( + '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i', + '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i'); + + $buffer = preg_replace_callback($replacements, array('moodle_session', 'sid_rewrite_link_tag'), $buffer); + $buffer = preg_replace('/]*>/i', + '\0', $buffer); + + return $buffer; + } + /** + * You won't call this function directly. This function is used to process + * text buffered by php in an output buffer. All output is run through this function + * before it is ouput. + * This function only processes absolute urls, it is used when we decide that + * php is processing other urls itself but needs some help with internal absolute urls still. + * @param string $buffer is the output sent from php + * @return string the output sent to the browser + */ + public static function sid_ob_rewrite_absolute($buffer){ + $replacements = array( + '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i', + '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i'); + + $buffer = preg_replace_callback($replacements, array('moodle_session', 'sid_rewrite_link_tag'), $buffer); + $buffer = preg_replace('/]*>/i', + '\0', $buffer); + return $buffer; + } + + /** + * A function to process link, a and script tags found + * by preg_replace_callback in {@link sid_ob_rewrite($buffer)}. + */ + public static function sid_rewrite_link_tag($matches){ + $url = $matches[4]; + $url = moodle_session::sid_process_url($url); + return $matches[1].$url.$matches[5]; + } + + /** + * You can call this function directly. This function is used to process + * urls to add a moodle session id to the url for internal links. + * @param string $url is a url + * @return string the processed url + */ + public static function sid_process_url($url) { + global $CFG; + + if ((preg_match('/^(http|https):/i', $url)) // absolute url + && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one + return $url; //don't attach sessid to non local urls + } + if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) { + return $url; //don't attach sessid to anchors + } + if (strpos($url, session_name())!==FALSE) { + return $url; //don't attach sessid to url that already has one sessid + } + if (strpos($url, "?")===FALSE) { + $append = "?".strip_tags(session_name() . '=' . session_id()); + } else { + $append = "&".strip_tags(session_name() . '=' . session_id()); + } + //put sessid before any anchor + $p = strpos($url, "#"); + if ($p!==FALSE){ + $anch = substr($url, $p); + $url = substr($url, 0, $p).$append.$anch ; + } else { + $url .= $append ; + } + return $url; + } + + /** + * Call this function before there has been any output to the browser to + * buffer output and add session ids to all internal links. + */ + public static function sid_start_ob(){ + global $CFG; + //don't attach sess id for bots + + if (!empty($_SERVER['HTTP_USER_AGENT'])) { + if (!empty($CFG->opentogoogle)) { + if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) { + @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid + $CFG->usesid=false; + return; + } + if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) { + @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid + $CFG->usesid=false; + return; + } + } + if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) { + @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid + $CFG->usesid=false; + return; + } + } + + @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid + + if (ini_get('session.use_trans_sid') != 0) { + // use trans sid as its available + ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry'); + ob_start(array('moodle_session', 'sid_ob_rewrite_absolute')); + } else { + //rewrite all links ourselves + ob_start(array('moodle_session', 'sid_ob_rewrite')); + } + } +} \ No newline at end of file