Add one more person
[squirrelmail.git] / include / init.php
index c6bb2d2341f5575ff6d17be0b3cc8d5660ca99e1..db0d9e6b12e0a59457e8b82d70b0b597d811ebb7 100644 (file)
@@ -5,7 +5,7 @@
  *
  * File should be loaded in every file in src/ or plugins that occupate an entire frame
  *
- * @copyright © 2006 The SquirrelMail Project Team
+ * @copyright 2006-2017 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
@@ -19,7 +19,6 @@ FIXME: disabling this for now, because we now have $sm_debug_mode, but the probl
 //error_reporting(E_ALL);
 
 
-
 /**
  * Make sure we have a page name
  *
@@ -75,6 +74,14 @@ if ((bool) ini_get('register_globals') &&
 global $null;
 $null = NULL;
 
+/**
+ * The global $server_os variable will be "windows" if
+ * we are working in a Windows environment or "*nix"
+ * otherwise.
+ */
+global $server_os;
+if (DIRECTORY_SEPARATOR == '\\') $server_os = 'windows'; else $server_os = '*nix';
+
 /**
  * [#1518885] session.use_cookies = off breaks SquirrelMail
  *
@@ -87,6 +94,46 @@ if (!(bool)ini_get('session.use_cookies') ||
     ini_set('session.use_cookies','1');
 }
 
+/**
+ * Initialize seed of random number generator.
+ * We use a number of things to randomize input: current time in ms,
+ * info about the remote client, info about the current process, the
+ * randomness of uniqid and stat of the current file.
+ *
+ * We seed this here only once per init, not only to save cycles
+ * but also to make the result of mt_rand more random (it now also
+ * depends on the number of times mt_rand was called before in this
+ * execution.
+ */
+$seed = microtime() . $_SERVER['REMOTE_PORT'] . $_SERVER['REMOTE_ADDR'] . getmypid();
+
+if (function_exists('getrusage')) {
+    /* Avoid warnings with Win32 */
+    $dat = @getrusage();
+    if (isset($dat) && is_array($dat)) { $seed .= implode('', $dat); }
+}
+
+if(!empty($_SERVER['UNIQUE_ID'])) {
+    $seed .= $_SERVER['UNIQUE_ID'];
+}
+
+$seed .= uniqid(mt_rand(),TRUE);
+$seed .= implode('', stat( __FILE__));
+
+// mt_srand() uses an integer to seed, so we need to distill our
+// very large seed to something useful (without taking a sub-string,
+// the integer conversion of such a large number is always 0 on
+// many systems, but strangely, 9 hex numbers - even if larger
+// than a signed 32 bit integer - seem to be an acceptable "integer"
+// seed (perhaps it is used as unsigned?)...
+// we may want to revisit this and always force it to be less than
+// 2,147,483,647
+//
+$seed = hexdec(substr(md5($seed), 0, 9));
+
+// PHP 4.2 and up don't require seeding, but their used seed algorithm
+// is of questionable quality, so we keep doing it ourselves. */
+mt_srand($seed);
 
 /**
  * calculate SM_PATH and calculate the base_uri
@@ -154,6 +201,7 @@ require(SM_PATH . 'include/constants.php');
 require(SM_PATH . 'functions/global.php');
 require(SM_PATH . 'functions/strings.php');
 require(SM_PATH . 'functions/arrays.php');
+require(SM_PATH . 'functions/files.php');
 
 /* load default configuration */
 require(SM_PATH . 'config/config_default.php');
@@ -175,18 +223,26 @@ if (file_exists(SM_PATH . 'config/config_local.php')) {
 
 /**
  * Set PHP error reporting level based on the SquirrelMail debug mode
+ * E_STRICT = 2048
+ * E_DEPRECATED = 8192
  */
 $error_level = 0;
 if ($sm_debug_mode & SM_DEBUG_MODE_SIMPLE)
     $error_level |= E_ERROR;
 if ($sm_debug_mode & SM_DEBUG_MODE_MODERATE
  || $sm_debug_mode & SM_DEBUG_MODE_ADVANCED)
-    $error_level |= E_ALL;
+    $error_level = ($error_level | E_ALL) & ~2048 & ~8192;
 if ($sm_debug_mode & SM_DEBUG_MODE_STRICT)
-    $error_level |= E_STRICT;
+    $error_level |= 2048 | 8192;
 error_reporting($error_level);
 
 
+/** 
+ * Detect SSL connections
+ */
+$is_secure_connection = is_ssl_secured_connection();
+
 require(SM_PATH . 'functions/plugin.php');
 require(SM_PATH . 'include/languages.php');
 require(SM_PATH . 'class/template/Template.class.php');
@@ -203,16 +259,35 @@ ini_set('magic_quotes_runtime','0');
 
 /* if running with magic_quotes_gpc then strip the slashes
    from POST and GET global arrays */
-if (get_magic_quotes_gpc()) {
+if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) {
     sqstripslashes($_GET);
     sqstripslashes($_POST);
 }
 
 
-/* strip any tags added to the url from PHP_SELF.
-This fixes hand crafted url XXS expoits for any
-   page that uses PHP_SELF as the FORM action */
-$_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
+/**
+ * Strip any tags added to the url from PHP_SELF.
+ * This fixes hand crafted url XXS expoits for any
+ * page that uses PHP_SELF as the FORM action
+ * Update: strip_tags() won't catch something like
+ * src/right_main.php?sort=0&startMessage=1&mailbox=INBOX&xxx="><script>window.open("http://example.com")</script>
+ * or
+ * contrib/decrypt_headers.php/%22%20onmouseover=%22alert(%27hello%20world%27)%22%3E
+ * because it doesn't bother with broken tags.
+ * sm_encode_html_special_chars() is the preferred method.
+ * QUERY_STRING also needs the same treatment since it is
+ * used in php_self().
+ * Update again: the encoding of ampersands that occurs
+ * using sm_encode_html_special_chars() corrupts the query strings
+ * in normal URIs, so we have to let those through.
+FIXME: will the de-sanitizing of ampersands create any security/XSS problems?
+ */
+if (isset($_SERVER['REQUEST_URI']))
+    $_SERVER['REQUEST_URI'] = str_replace('&amp;', '&', sm_encode_html_special_chars($_SERVER['REQUEST_URI']));
+if (isset($_SERVER['PHP_SELF']))
+    $_SERVER['PHP_SELF'] = str_replace('&amp;', '&', sm_encode_html_special_chars($_SERVER['PHP_SELF']));
+if (isset($_SERVER['QUERY_STRING']))
+    $_SERVER['QUERY_STRING'] = str_replace('&amp;', '&', sm_encode_html_special_chars($_SERVER['QUERY_STRING']));
 
 $PHP_SELF = php_self();
 
@@ -229,12 +304,12 @@ if (!isset($session_name) || !$session_name) {
  * When session.auto_start is On we want to destroy/close the session
  */
 $sSessionAutostartName = session_name();
-$sCookiePath = null;
-if (isset($sSessionAutostartName) && $sSessionAutostartName !== $session_name) {
+$sSessionAutostartID = session_id();
+if (!empty($sSessionAutostartID) && $sSessionAutostartName !== $session_name) {
     $sCookiePath = ini_get('session.cookie_path');
     $sCookieDomain = ini_get('session.cookie_domain');
     // reset the cookie
-    setcookie($sSessionAutostartName,'',time() - 604800,$sCookiePath,$sCookieDomain);
+    sqsetcookie($sSessionAutostartName,'',1,$sCookiePath,$sCookieDomain);
     @session_destroy();
     session_write_close();
 }
@@ -335,27 +410,18 @@ $SQM_INTERNAL_VERSION[2] = intval($SQM_INTERNAL_VERSION[2]);
 /* load prefs system; even when user not logged in, should be OK to do this here */
 require(SM_PATH . 'functions/prefs.php');
 
-// FIXME: config/plugin_hooks.php has not yet been loaded (see a few lines below); so this hook call should I think not be working -- has anyone actually tested it?  Is there any reason we cannot move this prefs code block down below "MAIN PLUGIN LOADING CODE HERE" (see below)?  Reading the code, I *think* it should be OK, but....   Also, note that this code would then be placed immediately next to the config_override hook, and since it makes little sense to execute two hooks in a row, I will propose removing config_override (although sadly, it is less clear to plugin authors that they should use the prefs_backend hook to do any configuration override work in their plugin)
-$prefs_backend = do_hook('prefs_backend', $null);
-if (isset($prefs_backend) && !empty($prefs_backend) && file_exists(SM_PATH . $prefs_backend)) {
-    require(SM_PATH . $prefs_backend);
-} elseif (isset($prefs_dsn) && !empty($prefs_dsn)) {
-    require(SM_PATH . 'functions/db_prefs.php');
-} else {
-    require(SM_PATH . 'functions/file_prefs.php');
-}
-
 
 /* if plugins are disabled only for one user and
  * the current user is NOT that user, turn them
  * back on
  */
-sqgetGlobalVar('username',$username,SQ_SESSION);
+sqgetGlobalVar('username', $username, SQ_SESSION);
 if ($disable_plugins && !empty($disable_plugins_user)
  && $username != $disable_plugins_user) {
     $disable_plugins = false;
 }
 
+
 /* remove all plugins if they are disabled */
 if ($disable_plugins) {
    $plugins = array();
@@ -368,6 +434,7 @@ if ($disable_plugins) {
 if (!$disable_plugins && file_exists(SM_PATH . 'plugins/compatibility/functions.php'))
     include_once(SM_PATH . 'plugins/compatibility/functions.php');
 
+
 /**
  * MAIN PLUGIN LOADING CODE HERE
  * On init, we no longer need to load all plugin setup files.
@@ -376,14 +443,29 @@ if (!$disable_plugins && file_exists(SM_PATH . 'plugins/compatibility/functions.
  */
 $squirrelmail_plugin_hooks = array();
 if (!$disable_plugins && file_exists(SM_PATH . 'config/plugin_hooks.php')) {
+//FIXME: if we keep the plugin hooks array static like this, it seems like we should also keep the template files list in a static file too (when a new user session is started or the template set is changed, the code will dynamically iterate through the directory heirarchy of the template directory and catalog all the template files therein (and store the "catalog" in PHP session) -- instead, we could do that once at config-time and keep that static so SM can just include the file just like the line below)
     require(SM_PATH . 'config/plugin_hooks.php');
 }
 
+
 /**
- * allow plugins to override main configuration; hook is placed
- * here to allow plugins to use session information to do their work
+ * Plugin authors note that the "config_override" hook used to be
+ * executed here, but please adapt your plugin to use this "prefs_backend" 
+ * hook instead, making sure that it does NOT return anything, since
+ * doing so will interfere with proper prefs system functionality.
+ * Of course, otherwise, this hook may be used to do any configuration
+ * overrides as needed, as well as set up a custom preferences backend.
  */
-do_hook('config_override', $null);
+$prefs_backend = do_hook('prefs_backend', $null);
+if (isset($prefs_backend) && !empty($prefs_backend) && file_exists(SM_PATH . $prefs_backend)) {
+    require(SM_PATH . $prefs_backend);
+} elseif (isset($prefs_dsn) && !empty($prefs_dsn)) {
+    require(SM_PATH . 'functions/db_prefs.php');
+} else {
+    require(SM_PATH . 'functions/file_prefs.php');
+}
+
+
 
 /**
  * DISABLED.
@@ -411,10 +493,24 @@ if (! sqgetGlobalVar('squirrelmail_language',$squirrelmail_language,SQ_COOKIE))
 }
 
 
+/**
+ * In some cases, buffering all output allows more complex functionality,
+ * especially for plugins that want to add headers on hooks that are beyond
+ * the point of output having been sent to the browser otherwise.
+ *
+ * Note that we don't turn this on any earlier since we want to allow plugins
+ * to turn it on themselves via a configuration override on the prefs_backend
+ * hook.
+ *
+ */
+if ($buffer_output) ob_start(!empty($buffered_output_handler) ? $buffered_output_handler : NULL);
+
+
 /**
  * Do something special for some pages. This is based on the PAGE_NAME constant
  * set at the top of every page.
  */
+$set_up_langage_after_template_setup = FALSE;
 switch (PAGE_NAME) {
     case 'style':
 
@@ -464,7 +560,7 @@ switch (PAGE_NAME) {
         // reset template file cache
         //
         $sTemplateID = Template::get_default_template_set();
-        Template::cache_template_file_hierarchy(TRUE);
+        Template::cache_template_file_hierarchy($sTemplateID, TRUE);
 
         /**
          * Make sure icon variables are setup for the login page.
@@ -477,22 +573,6 @@ switch (PAGE_NAME) {
          */
         $icon_theme_path = (!$use_icons || $icon_theme=='none') ? NULL : ($icon_theme == 'template' ? SM_PATH . Template::calculate_template_images_directory($sTemplateID) : $icon_theme);
 
-        /**
-         * cleanup old cookies with a cookie path the same as the standard php.ini
-         * cookie path. All previous SquirrelMail version used the standard php.ini
-         * cookie path for storing the session name. That behaviour changed.
-         */
-        if ($sCookiePath !== SM_BASE_URI) {
-            /**
-             * do not delete the standard sessions with session.name is i.e. PHPSESSID
-             * because they probably belong to other php apps
-             */
-            if (ini_get('session.name') !== $sSessionAutostartName) {
-                //  This does not work. Sometimes the cookie with SQSESSID=deleted and path /
-                // is picked up in webmail.php => login will fail
-                //sqsetcookie(ini_get('session.name'),'',0,$sCookiePath);
-            }
-        }
         break;
     default:
         require(SM_PATH . 'functions/display_messages.php' );
@@ -501,16 +581,28 @@ switch (PAGE_NAME) {
 
 
         /**
-         * Check if we are logged in
+         * Check if we are logged in and does optional referrer check
          */
         require(SM_PATH . 'functions/auth.php');
 
-        if ( !sqsession_is_registered('user_is_logged_in') ) {
+        global $check_referrer, $domain;
+        if (!sqgetGlobalVar('HTTP_REFERER', $referrer, SQ_SERVER)) $referrer = '';
+        if ($check_referrer == '###DOMAIN###') $check_referrer = $domain;
+        if (!empty($check_referrer)) {
+            $ssl_check_referrer = 'https://' . $check_referrer;
+            $check_referrer = 'http://' . $check_referrer;
+        }
+        if (!sqsession_is_registered('user_is_logged_in')
+         || ($check_referrer && !empty($referrer)
+          && strpos(strtolower($referrer), strtolower($check_referrer)) !== 0
+          && strpos(strtolower($referrer), strtolower($ssl_check_referrer)) !== 0)) {
 
             // use $message to indicate what logout text the user
             // will see... if 0, typical "You must be logged in"
             // if 1, information that the user session was saved
-            // and will be resumed after (re)login
+            // and will be resumed after (re)login, if 2, there
+            // seems to have been a XSS or phishing attack (bad
+            // referrer)
             //
             $message = 0;
 
@@ -527,6 +619,13 @@ switch (PAGE_NAME) {
                 if ($session_expired_location == 'compose')
                     $message = 1;
             }
+
+            // was bad referrer the reason we were rejected?
+            //
+            if (sqsession_is_registered('user_is_logged_in')
+             && $check_referrer && !empty($referrer))
+                $message = 2;
+
             // signout page will deal with users who aren't logged
             // in on its own; don't show error here
             //
@@ -540,16 +639,22 @@ switch (PAGE_NAME) {
             /*
              * $sTemplateID is not initialized when a user is not logged in, so we
              * will use the config file defaults here.  If the neccesary variables
-             * are net set, force a default value.
+             * are not set, force a default value.
              */
-            $sTemplateID = Template::get_default_template_set();
+            if (PAGE_NAME == 'squirrelmail_rpc') {
+                $sTemplateID = Template::get_rpc_template_set();
+            } else {
+                $sTemplateID = Template::get_default_template_set();
+            }
             $oTemplate = Template::construct_template($sTemplateID);
 
             set_up_language($squirrelmail_language, true);
             if (!$message)
                 logout_error( _("You must be logged in to access this page.") );
-            else
+            else if ($message == 1)
                 logout_error( _("Your session has expired, but will be resumed after logging in again.") );
+            else if ($message == 2)
+                logout_error( _("The current page request appears to have originated from an unrecognized source.") );
             exit;
         }
 
@@ -573,7 +678,6 @@ switch (PAGE_NAME) {
          */
         require(SM_PATH . 'include/load_prefs.php');
 
-// i do not understand the frames language cookie story
         /**
          * We'll need this to later have a noframes version
          *
@@ -585,20 +689,8 @@ switch (PAGE_NAME) {
          if ($my_language != $squirrelmail_language) {
              sqsetcookie('squirrelmail_language', $my_language, time()+2592000, $base_uri);
          }
-// /dont understand
 
-        /**
-         * Set up the language.
-         */
-        $err=set_up_language(getPref($data_dir, $username, 'language'));
-
-        // Japanese translation used without mbstring support
-        if ($err==2) {
-            $sError = "<p>Your administrator needs to have PHP installed with the multibyte string extension enabled (using configure option --enable-mbstring).</p>\n"
-                    . "<p>This system has assumed that you accidently switched to Japanese and has reverted your language preference to English.</p>\n"
-                    . "<p>Please refresh this page in order to continue using your webmail.</p>\n";
-            error_box($sError);
-        }
+        $set_up_langage_after_template_setup = TRUE;
 
         $timeZone = getPref($data_dir, $username, 'timezone');
 
@@ -656,7 +748,11 @@ switch (PAGE_NAME) {
  * so we shouldn't change it here.
  */
 if (!isset($sTemplateID)) {
-    $sTemplateID = Template::get_default_template_set();
+    if (PAGE_NAME == 'squirrelmail_rpc') {
+        $sTemplateID = Template::get_rpc_template_set();
+    } else {
+        $sTemplateID = Template::get_default_template_set();
+    }
     $icon_theme_path = !$use_icons ? NULL : Template::calculate_template_images_directory($sTemplateID);
 }
 
@@ -667,6 +763,7 @@ if (empty($oTemplate)) {
 }
 
 // We want some variables to always be available to the template
+//
 $oTemplate->assign('javascript_on', 
     (sqGetGlobalVar('user_is_logged_in', $user_is_logged_in, SQ_SESSION)
      ?  checkForJavascript() : 0));
@@ -676,11 +773,39 @@ foreach ($always_include as $var) {
     $oTemplate->assign($var, (isset($$var) ? $$var : NULL));
 }
 
+// A few output elements are used often, so just get them once here
+//
+$nbsp = $oTemplate->fetch('non_breaking_space.tpl');
+$br = $oTemplate->fetch('line_break.tpl');
+
+
+/**
+ * Set up the language.
+ *
+ * This code block corresponds to the *default* block of the switch
+ * statement above, but the language cannot be set up until after the
+ * template is instantiated, so we set $set_up_langage_after_template_setup
+ * above and do the linguistic stuff now.
+ */
+if ($set_up_langage_after_template_setup) {
+    $err=set_up_language(getPref($data_dir, $username, 'language'));
+
+    // Japanese translation used without mbstring support
+    if ($err==2) {
+        $sError = "<p>Your administrator needs to have PHP installed with the multibyte string extension enabled (using configure option --enable-mbstring).</p>\n"
+                . "<p>This system has assumed that you accidently switched to Japanese and has reverted your language preference to English.</p>\n"
+                . "<p>Please refresh this page in order to continue using your webmail.</p>\n";
+        error_box($sError);
+    }
+}
+
+
 /**
  * Initialize our custom error handler object
  */
 $oErrorHandler = new ErrorHandler($oTemplate,'error_message.tpl');
 
+
 /**
  * Activate custom error handling
  */
@@ -708,6 +833,7 @@ function checkForJavascript($reset = FALSE) {
   if ( !$reset && sqGetGlobalVar('javascript_on', $javascript_on, SQ_SESSION) )
     return $javascript_on;
 
+  //FIXME: this isn't used anywhere else in this function; can we remove it?  why is it here?
   $user_is_logged_in = FALSE;
   if ( $reset || !isset($javascript_setting) )
     $javascript_setting = getPref($data_dir, $username, 'javascript_setting', SMPREF_JS_AUTODETECT);