Added comment to SECURE_ONLY constant.
[fa-stable.git] / includes / session.inc
index 2688a24e94072dee83851ad071ec027e04537b45..5f9240eb6ad4d6b894bc97d080d775ea2f4caf12 100644 (file)
@@ -9,6 +9,9 @@
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
        See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
 ***********************************************************************/
+define('VARLIB_PATH', $path_to_root.'/tmp');
+define('VARLOG_PATH', $path_to_root.'/tmp');
+define('SECURE_ONLY', true); // if you really need also http (unsecure) access allowed, you can set this to NULL
 
 class SessionManager
 {
@@ -21,7 +24,11 @@ class SessionManager
                $https = isset($secure) ? $secure : (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
 
                // Set session cookie options
-               session_set_cookie_params($limit, $path, $domain, $https, true);
+               if (version_compare(PHP_VERSION, '5.2', '<')) // avoid failure on older php versions
+                       session_set_cookie_params($limit, $path, $domain, $https);
+               else
+                       session_set_cookie_params($limit, $path, $domain, $https, true);
+
                session_start();
 
                // Make sure the session hasn't expired, and destroy it if it has
@@ -33,7 +40,7 @@ class SessionManager
                                // Reset session data and regenerate id
                                $_SESSION = array();
                                $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
-                               $_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
+                               $_SESSION['userAgent'] = @$_SERVER['HTTP_USER_AGENT'];
                                $this->regenerateSession();
 
                        // Give a 5% chance of the session id changing on any request
@@ -59,7 +66,7 @@ class SessionManager
                if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
                        return false;
 
-               if ( $_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT'])
+               if ( $_SESSION['userAgent'] != @$_SERVER['HTTP_USER_AGENT'])
                        return false;
 
                return true;
@@ -77,7 +84,6 @@ class SessionManager
 
                // Create new session without destroying the old one
                session_regenerate_id();
                // Grab current session ID and close both sessions to allow other scripts to use them
                $newSession = session_id();
                session_write_close();
@@ -128,45 +134,75 @@ function kill_login()
 function login_fail()
 {
        global $path_to_root;
-       
+
        header("HTTP/1.1 401 Authorization Required");
        echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Password") . "<b></font><br><br>";
        echo "<b>" . _("The user and password combination is not valid for the system.") . "<b><br><br>";
-
        echo _("If you are not an authorized user, please contact your system administrator to obtain an account to enable you to use the system.");
        echo "<br><a href='$path_to_root/index.php'>" . _("Try again") . "</a>";
        echo "</center>";
+       kill_login();
+       die();
+}
+
+function password_reset_fail()
+{
+       global $path_to_root;
+       
+       echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Email") . "<b></font><br><br>";
+       echo "<b>" . _("The email address does not exist in the system, or is used by more than one user.") . "<b><br><br>";
+
+       echo _("Plase try again or contact your system administrator to obtain new password.");
+       echo "<br><a href='$path_to_root/index.php?reset=1'>" . _("Try again") . "</a>";
+       echo "</center>";
 
        kill_login();
        die();
 }
-//----------------------------------------------------------------------------------------
-// set to reasonable values if not set in config file (pre-2.3.12 installations)
 
-if (!isset($login_delay))
+function password_reset_success()
 {
-       $login_delay = 10;
-       $login_max_attempts = 3;
+       global $path_to_root;
+
+       echo "<center><br><br><font size='5' color='green'><b>" . _("New password sent") . "<b></font><br><br>";
+       echo "<b>" . _("A new password has been sent to your mailbox.") . "<b><br><br>";
+
+       echo "<br><a href='$path_to_root/index.php'>" . _("Login here") . "</a>";
+       echo "</center>";
+       
+       kill_login();
+       die();
 }
 
 function check_faillog()
 {
-       global $login_delay, $login_faillog, $login_max_attempts;
+       global $SysPrefs, $login_faillog;
 
        $user = $_SESSION["wa_current_user"]->user;
 
-       if (@$login_delay && (@$login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$login_max_attempts) && (time() < $login_faillog[$user]['last'] + $login_delay))
+       $_SESSION["wa_current_user"]->login_attempt++;
+       if (@$SysPrefs->login_delay && (@$login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$SysPrefs->login_max_attempts) && (time() < $login_faillog[$user]['last'] + $SysPrefs->login_delay))
                return true;
 
        return false;
 }
+
+/*
+       Ensure file is re-read on next request if php caching is active
+*/
+function cache_invalidate($filename)
+{
+       if (function_exists('opcache_invalidate'))      // OpCode extension
+               opcache_invalidate($filename);
+}
+
 /*
        Simple brute force attack detection is performed before connection to company database is open. Therefore access counters have to be stored in file.
        Login attempts counter is created for every new user IP, which partialy prevent DOS attacks.
 */
 function write_login_filelog($login, $result)
 {
-       global $login_faillog, $login_max_attempts, $path_to_root;
+       global $login_faillog, $SysPrefs, $path_to_root;
 
        $user = $_SESSION["wa_current_user"]->user;
 
@@ -177,7 +213,7 @@ function write_login_filelog($login, $result)
 
        if (!$result)
        {
-               if ($login_faillog[$user][$ip] < @$login_max_attempts) {
+               if ($login_faillog[$user][$ip] < @$SysPrefs->login_max_attempts) {
 
                        $login_faillog[$user][$ip]++;
                } else {
@@ -193,11 +229,12 @@ function write_login_filelog($login, $result)
        $msg .= "*/\n";
        $msg .= "\$login_faillog = " .var_export($login_faillog, true). ";\n";
 
-       $filename = $path_to_root."/faillog.php";
+       $filename = VARLIB_PATH."/faillog.php";
 
-       if ((!file_exists($filename) && is_writable($path_to_root)) || is_writable($filename))
+       if ((!file_exists($filename) && is_writable(VARLIB_PATH)) || is_writable($filename))
        {
                file_put_contents($filename, $msg);
+               cache_invalidate($filename);
        }
 }
 
@@ -216,7 +253,8 @@ function check_page_security($page_security)
                         _("Security settings have not been defined for your user account.")
                                . "<br>" . _("Please contact your system administrator.")       
                        : _("Please remove \$security_groups and \$security_headings arrays from config.php file!");
-       } elseif (!$_SESSION['SysPrefs']->db_ok && !$_SESSION["wa_current_user"]->can_access('SA_SOFTWAREUPGRADE')) {
+       } elseif (!$SysPrefs->db_ok && !$_SESSION["wa_current_user"]->can_access('SA_SOFTWAREUPGRADE')) 
+       {
                $msg = _('Access to application has been blocked until database upgrade is completed by system administrator.');
        }
        
@@ -237,7 +275,7 @@ function check_page_security($page_security)
                end_page(@$_REQUEST['popup']);
                exit;
        }
-       if (!$_SESSION['SysPrefs']->db_ok 
+       if (!$SysPrefs->db_ok 
                && !in_array($page_security, array('SA_SOFTWAREUPGRADE', 'SA_OPEN', 'SA_BACKUP')))
        {
                display_error(_('System is blocked after source upgrade until database is updated on System/Software Upgrade page'));
@@ -274,7 +312,7 @@ function set_page_security($value=null, $trans = array(), $gtrans = array())
 //
 function strip_quotes($data)
 {
-       if(get_magic_quotes_gpc()) {
+       if(version_compare(phpversion(), '5.4', '<') && get_magic_quotes_gpc()) {
                if(is_array($data)) {
                        foreach($data as $k => $v) {
                                $data[$k] = strip_quotes($data[$k]);
@@ -285,14 +323,25 @@ function strip_quotes($data)
        return $data;
 }
 
+/*
+       htmlspecialchars does not support certain encodings.
+       ISO-8859-2 fortunately has the same special characters positions as 
+       ISO-8859-1, so fix is easy. If any other unsupported encoding is used,
+       add workaround here.
+*/
+function html_specials_encode($str)
+{
+       return htmlspecialchars($str, ENT_QUOTES, $_SESSION['language']->encoding=='iso-8859-2' ?
+                'ISO-8859-1' : $_SESSION['language']->encoding);
+}
+
 function html_cleanup(&$parms)
 {
        foreach($parms as $name => $value) {
-//             $value = @html_entity_decode($value, ENT_QUOTES, $_SESSION['language']->encoding);
                if (is_array($value))
                        html_cleanup($parms[$name]);
                else
-                       $parms[$name] = @htmlspecialchars($value, ENT_QUOTES, $_SESSION['language']->encoding);
+                       $parms[$name] = html_specials_encode($value);
        }
        reset($parms); // needed for direct key() usage later throughout the sources
 }
@@ -325,13 +374,13 @@ if (isset($_GET['path_to_root']) || isset($_POST['path_to_root']))
 include_once($path_to_root . "/includes/errors.inc");
 // colect all error msgs
 set_error_handler('error_handler' /*, errtypes */);
+set_exception_handler('exception_handler');
 
 include_once($path_to_root . "/includes/current_user.inc");
 include_once($path_to_root . "/frontaccounting.php");
 include_once($path_to_root . "/admin/db/security_db.inc");
-include_once($path_to_root . "/includes/lang/language.php");
+include_once($path_to_root . "/includes/lang/language.inc");
 include_once($path_to_root . "/config_db.php");
-@include_once($path_to_root . "/faillog.php");
 include_once($path_to_root . "/includes/ajax.inc");
 include_once($path_to_root . "/includes/ui/ui_msgs.inc");
 include_once($path_to_root . "/includes/prefs/sysprefs.inc");
@@ -346,26 +395,60 @@ foreach ($installed_extensions as $ext)
                include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
 }
 
+ini_set('session.gc_maxlifetime', 36000); // moved from below.
+
+$Session_manager = new SessionManager();
+$Session_manager->sessionStart('FA'.md5(dirname(__FILE__)), 0, '/', null, SECURE_ONLY);
+
+$_SESSION['SysPrefs'] = new sys_prefs();
+
+$SysPrefs = &$_SESSION['SysPrefs'];
+
+//----------------------------------------------------------------------------------------
+// set to reasonable values if not set in config file (pre-2.3.12 installations)
+
+if ((!isset($SysPrefs->login_delay)) || ($SysPrefs->login_delay < 0))
+    $SysPrefs->login_delay = 10;
+
+if ((!isset($SysPrefs->login_max_attempts)) || ($SysPrefs->login_max_attempts < 0))
+    $SysPrefs->login_max_attempts = 3; 
+
+if ($SysPrefs->go_debug > 0)
+       $cur_error_level = -1;
+else
+       $cur_error_level = E_USER_WARNING|E_USER_ERROR|E_USER_NOTICE;
+
+error_reporting($cur_error_level);
+ini_set("display_errors", "On");
+
+if ($SysPrefs->error_logfile != '') {
+       ini_set("error_log", $SysPrefs->error_logfile);
+       ini_set("ignore_repeated_errors", "On");
+       ini_set("log_errors", "On");
+}
+
 /*
        Uncomment the setting below when using FA on shared hosting
        to avoid unexpeced session timeouts.
        Make sure this directory exists and is writable!
 */
-// ini_set('session.save_path', dirname(__FILE__).'/../tmp/');
+// ini_set('session.save_path', VARLIB_PATH.'/');
 
-ini_set('session.gc_maxlifetime', 36000); // 10hrs
+// ini_set('session.gc_maxlifetime', 36000); // 10hrs - moved to before session_manager
 
-$Session_manager = new SessionManager();
-$Session_manager->sessionStart('FA'.md5(dirname(__FILE__)));
+hook_session_start(@$_POST["company_login_name"]);
 
 // this is to fix the "back-do-you-want-to-refresh" issue - thanx PHPFreaks
 header("Cache-control: private");
 
-include_once($path_to_root . "/config.php");
 get_text_init();
 
+if ($SysPrefs->login_delay > 0 && file_exists(VARLIB_PATH."/faillog.php"))
+       include_once(VARLIB_PATH."/faillog.php");
+
 // Page Initialisation
-if (!isset($_SESSION['language']) || !method_exists($_SESSION['language'], 'set_language')) 
+if (!isset($_SESSION['wa_current_user']) || !$_SESSION['wa_current_user']->logged_in()
+       || !isset($_SESSION['language']) || !method_exists($_SESSION['language'], 'set_language'))
 {
        $l = array_search_value($dflt_lang, $installed_languages,  'code');
        $_SESSION['language'] = new language($l['name'], $l['code'], $l['encoding'],
@@ -378,6 +461,7 @@ $_SESSION['language']->set_language($_SESSION['language']->code);
 include_once($path_to_root . "/includes/access_levels.inc");
 include_once($path_to_root . "/version.php");
 include_once($path_to_root . "/includes/main.inc");
+include_once($path_to_root . "/includes/app_entries.inc");
 
 // Ajax communication object
 $Ajax = new Ajax();
@@ -405,28 +489,55 @@ html_cleanup($_SERVER);
 
 // logout.php is the only page we should have always 
 // accessable regardless of access level and current login status.
-if (strstr($_SERVER['PHP_SELF'], 'logout.php') == false){
+if (!defined('FA_LOGOUT_PHP_FILE')){
 
        login_timeout();
 
-       if (!$_SESSION["wa_current_user"]->old_db)
-               include_once($path_to_root . '/company/'.user_company().'/installed_extensions.php');
+       if (!$_SESSION["wa_current_user"]->old_db && file_exists($path_to_root . '/company/'.user_company().'/installed_extensions.php'))
+               include($path_to_root . '/company/'.user_company().'/installed_extensions.php');
 
        install_hooks();
 
        if (!$_SESSION["wa_current_user"]->logged_in())
        {
+      if (@$SysPrefs->allow_password_reset && !$SysPrefs->allow_demo_mode
+        && (isset($_GET['reset']) || isset($_POST['email_entry_field']))) {
+                 if (!isset($_POST["email_entry_field"])) {
+        include($path_to_root . "/access/password_reset.php");
+        exit();
+      }
+      else {
+        if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
+          for ($i = 0; $i < count($db_connections); $i++) {
+            if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
+              $_POST["company_login_name"] = $i;
+              unset($_POST["company_login_nickname"]);
+              break 1; // cannot pass variables to break from PHP v5.4 onwards
+            }
+          }
+        }
+        $_succeed = isset($db_connections[$_POST["company_login_name"]]) &&
+          $_SESSION["wa_current_user"]->reset_password($_POST["company_login_name"],
+          $_POST["email_entry_field"]);
+        if ($_succeed)
+        {
+          password_reset_success();
+        }
+
+        password_reset_fail();
+      }
+    }
                // Show login screen
                if (!isset($_POST["user_name_entry_field"]) or $_POST["user_name_entry_field"] == "")
                {
                        // strip ajax marker from uri, to force synchronous page reload
                        $_SESSION['timeout'] = array( 'uri'=>preg_replace('/JsHttpRequest=(?:(\d+)-)?([^&]+)/s',
-                                       '', @htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, $_SESSION['language']->encoding)), 
+                                       '', html_specials_encode($_SERVER['REQUEST_URI'])),
                                'post' => $_POST);
-
+               if (in_ajax())
+                       $Ajax->popup($path_to_root ."/access/timeout.php");
+               else
                        include($path_to_root . "/access/login.php");
-                       if (in_ajax())
-                               $Ajax->activate('_page_body');
                        exit;
                } else {
                        if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
@@ -446,24 +557,38 @@ if (strstr($_SERVER['PHP_SELF'], 'logout.php') == false){
                        if (!$succeed)
                        {
                        // Incorrect password
-                               login_fail();
+                               if (isset($_SESSION['timeout'])) {
+                                       include($path_to_root . "/access/login.php");
+                                       exit;
+                               } else
+                                       login_fail();
+                       }
+                       elseif(isset($_SESSION['timeout']) && !$_SESSION['timeout']['post'])
+                       {
+                               // in case of GET request redirect to avoid confirmation dialog 
+                               // after return from menu option
+                               header("HTTP/1.1 307 Temporary Redirect");
+                               header("Location: ".$_SESSION['timeout']['uri']);
+                               exit();
                        }
                        $lang = &$_SESSION['language'];
                        $lang->set_language($_SESSION['language']->code);
                }
        } else
+       {
                set_global_connection();
 
+               if (db_fixed())
+                       db_set_encoding($_SESSION['language']->encoding);
+
+               $SysPrefs->refresh();
+       }
        if (!isset($_SESSION["App"])) {
                $_SESSION["App"] = new front_accounting();
                $_SESSION["App"]->init();
        }
 }
 
-$SysPrefs = &$_SESSION['SysPrefs'];
-
 // POST vars cleanup needed for direct reuse.
 // We quote all values later with db_escape() before db update.
 $_POST = strip_quotes($_POST);
-
-?>
\ No newline at end of file