2 /**********************************************************************
3 Copyright (C) FrontAccounting, LLC.
4 Released under the terms of the GNU General Public License, GPL,
5 as published by the Free Software Foundation, either version 3
6 of the License, or (at your option) any later version.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
15 function sessionStart($name, $limit = 0, $path = '/', $domain = null, $secure = null)
17 // Set the cookie name
21 $https = isset($secure) ? $secure : (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
23 // Set session cookie options
24 if (version_compare(PHP_VERSION, '5.2', '<')) // avoid failure on older php versions
25 session_set_cookie_params($limit, $path, $domain, $https);
27 session_set_cookie_params($limit, $path, $domain, $https, true);
31 // Make sure the session hasn't expired, and destroy it if it has
32 if ($this->validateSession())
34 // Check to see if the session is new or a hijacking attempt
35 if(!$this->preventHijacking())
37 // Reset session data and regenerate id
39 $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
40 $_SESSION['userAgent'] = @$_SERVER['HTTP_USER_AGENT'];
41 $this->regenerateSession();
43 // Give a 5% chance of the session id changing on any request
45 elseif (rand(1, 100) <= 5)
47 $this->regenerateSession();
58 function preventHijacking()
60 if (!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
63 if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
66 if ( $_SESSION['userAgent'] != @$_SERVER['HTTP_USER_AGENT'])
72 function regenerateSession()
74 // If this session is obsolete it means there already is a new id
75 if (isset($_SESSION['OBSOLETE']) && ($_SESSION['OBSOLETE'] == true))
78 // Set current session to expire in 10 seconds
79 $_SESSION['OBSOLETE'] = true;
80 $_SESSION['EXPIRES'] = time() + 10;
82 // Create new session without destroying the old one
83 session_regenerate_id();
84 // Grab current session ID and close both sessions to allow other scripts to use them
85 $newSession = session_id();
86 session_write_close();
87 // Set session ID to the new one, and start it back up again
89 session_id($newSession);
92 // Now we unset the obsolete and expiration values for the session we want to keep
93 unset($_SESSION['OBSOLETE']);
94 unset($_SESSION['EXPIRES']);
97 function validateSession()
99 if (isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES']) )
102 if (isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time())
109 function output_html($text)
111 global $before_box, $Ajax, $messages;
112 // Fatal errors are not send to error_handler,
113 // so we must check the output
114 if ($text && preg_match('/\bFatal error(<.*?>)?:(.*)/i', $text, $m)) {
115 $Ajax->aCommands = array(); // Don't update page via ajax on errors
116 $text = preg_replace('/\bFatal error(<.*?>)?:(.*)/i','', $text);
117 $messages[] = array(E_ERROR, $m[2], null, null);
120 return in_ajax() ? fmt_errors() : ($before_box.fmt_errors().$text);
122 //----------------------------------------------------------------------------------------
124 function kill_login()
129 //----------------------------------------------------------------------------------------
131 function login_fail()
133 global $path_to_root;
135 header("HTTP/1.1 401 Authorization Required");
136 echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Password") . "<b></font><br><br>";
137 echo "<b>" . _("The user and password combination is not valid for the system.") . "<b><br><br>";
139 echo _("If you are not an authorized user, please contact your system administrator to obtain an account to enable you to use the system.");
140 echo "<br><a href='$path_to_root/index.php'>" . _("Try again") . "</a>";
147 function password_reset_fail()
149 global $path_to_root;
151 echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Email") . "<b></font><br><br>";
152 echo "<b>" . _("The email address does not exist in the system.") . "<b><br><br>";
154 echo _("If you are not an authorized user, please contact your system administrator to obtain an account to enable you to use the system.");
155 echo "<br><a href='$path_to_root/index.php?reset=1'>" . _("Try again") . "</a>";
162 function password_reset_success()
164 global $path_to_root;
166 echo "<center><br><br><font size='5' color='green'><b>" . _("New password sent") . "<b></font><br><br>";
167 echo "<b>" . _("A new password has been sent to your mailbox.") . "<b><br><br>";
169 echo "<br><a href='$path_to_root/index.php'>" . _("Login here") . "</a>";
176 function check_faillog()
178 global $SysPrefs, $login_faillog;
180 $user = $_SESSION["wa_current_user"]->user;
182 if (@$SysPrefs->login_delay && (@$login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$SysPrefs->login_max_attempts) && (time() < $login_faillog[$user]['last'] + $SysPrefs->login_delay))
188 Simple brute force attack detection is performed before connection to company database is open. Therefore access counters have to be stored in file.
189 Login attempts counter is created for every new user IP, which partialy prevent DOS attacks.
191 function write_login_filelog($login, $result)
193 global $login_faillog, $SysPrefs, $path_to_root;
195 $user = $_SESSION["wa_current_user"]->user;
197 $ip = $_SERVER['REMOTE_ADDR'];
199 if (!isset($login_faillog[$user][$ip]) || $result) // init or reset on successfull login
200 $login_faillog[$user] = array($ip => 0, 'last' => '');
204 if ($login_faillog[$user][$ip] < @$SysPrefs->login_max_attempts) {
206 $login_faillog[$user][$ip]++;
208 $login_faillog[$user][$ip] = 0; // comment out to restart counter only after successfull login.
209 error_log(sprintf(_("Brute force attack on account '%s' detected. Access for non-logged users temporarily blocked." ), $login));
211 $login_faillog[$user]['last'] = time();
216 $msg .= "Login attempts info.\n";
218 $msg .= "\$login_faillog = " .var_export($login_faillog, true). ";\n";
220 $filename = $path_to_root."/tmp/faillog.php";
222 if ((!file_exists($filename) && is_writable($path_to_root.'/tmp')) || is_writable($filename))
224 file_put_contents($filename, $msg);
228 //----------------------------------------------------------------------------------------
230 function check_page_security($page_security)
236 if (!$_SESSION["wa_current_user"]->check_user_access())
238 // notification after upgrade from pre-2.2 version
239 $msg = $_SESSION["wa_current_user"]->old_db ?
240 _("Security settings have not been defined for your user account.")
241 . "<br>" . _("Please contact your system administrator.")
242 : _("Please remove \$security_groups and \$security_headings arrays from config.php file!");
243 } elseif (!$_SESSION['SysPrefs']->db_ok && !$_SESSION["wa_current_user"]->can_access('SA_SOFTWAREUPGRADE'))
245 $msg = _('Access to application has been blocked until database upgrade is completed by system administrator.');
250 end_page(@$_REQUEST['popup']);
255 if (!$_SESSION["wa_current_user"]->can_access_page($page_security))
258 echo "<center><br><br><br><b>";
259 echo _("The security settings on your account do not permit you to access this function");
261 echo "<br><br><br><br></center>";
262 end_page(@$_REQUEST['popup']);
265 if (!$_SESSION['SysPrefs']->db_ok
266 && !in_array($page_security, array('SA_SOFTWAREUPGRADE', 'SA_OPEN', 'SA_BACKUP')))
268 display_error(_('System is blocked after source upgrade until database is updated on System/Software Upgrade page'));
275 Helper function for setting page security level depeding on
276 GET start variable and/or some value stored in session variable.
277 Before the call $page_security should be set to default page_security value.
279 function set_page_security($value=null, $trans = array(), $gtrans = array())
281 global $page_security;
283 // first check is this is not start page call
284 foreach($gtrans as $key => $area)
285 if (isset($_GET[$key])) {
286 $page_security = $area;
290 // then check session value
291 if (isset($trans[$value])) {
292 $page_security = $trans[$value];
297 //-----------------------------------------------------------------------------
298 // Removing magic quotes from nested arrays/variables
300 function strip_quotes($data)
302 if(get_magic_quotes_gpc()) {
303 if(is_array($data)) {
304 foreach($data as $k => $v) {
305 $data[$k] = strip_quotes($data[$k]);
308 return stripslashes($data);
314 htmlspecialchars does not support certain encodings.
315 ISO-8859-2 fortunately has the same special characters positions as
316 ISO-8859-1, so fix is easy. If any other unsupported encoding is used,
319 function html_specials_encode($str)
321 return htmlspecialchars($str, ENT_QUOTES, $_SESSION['language']->encoding=='iso-8859-2' ?
322 'ISO-8859-1' : $_SESSION['language']->encoding);
325 function html_cleanup(&$parms)
327 foreach($parms as $name => $value) {
328 if (is_array($value))
329 html_cleanup($parms[$name]);
331 $parms[$name] = html_specials_encode($value);
333 reset($parms); // needed for direct key() usage later throughout the sources
336 //============================================================================
339 function login_timeout()
341 // skip timeout on logout page
342 if ($_SESSION["wa_current_user"]->logged) {
343 $tout = $_SESSION["wa_current_user"]->timeout;
344 if ($tout && (time() > $_SESSION["wa_current_user"]->last_act + $tout))
346 $_SESSION["wa_current_user"]->logged = false;
348 $_SESSION["wa_current_user"]->last_act = time();
351 //============================================================================
352 if (!isset($path_to_root))
357 // Prevent register_globals vulnerability
358 if (isset($_GET['path_to_root']) || isset($_POST['path_to_root']))
359 die("Restricted access");
361 include_once($path_to_root . "/includes/errors.inc");
362 // colect all error msgs
363 set_error_handler('error_handler' /*, errtypes */);
365 include_once($path_to_root . "/includes/current_user.inc");
366 include_once($path_to_root . "/frontaccounting.php");
367 include_once($path_to_root . "/admin/db/security_db.inc");
368 include_once($path_to_root . "/includes/lang/language.inc");
369 include_once($path_to_root . "/config_db.php");
370 include_once($path_to_root . "/includes/ajax.inc");
371 include_once($path_to_root . "/includes/ui/ui_msgs.inc");
372 include_once($path_to_root . "/includes/prefs/sysprefs.inc");
374 include_once($path_to_root . "/includes/hooks.inc");
376 // include all extensions hook files.
378 foreach ($installed_extensions as $ext)
380 if (file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
381 include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
384 $_SESSION['SysPrefs'] = new sys_prefs();
386 $SysPrefs = &$_SESSION['SysPrefs'];
388 //----------------------------------------------------------------------------------------
389 // set to reasonable values if not set in config file (pre-2.3.12 installations)
391 if ((!isset($SysPrefs->login_delay)) || ($SysPrefs->login_delay < 0))
392 $SysPrefs->login_delay = 10;
394 if ((!isset($SysPrefs->login_max_attempts)) || ($SysPrefs->login_max_attempts < 0))
395 $SysPrefs->login_max_attempts = 3;
397 if ($SysPrefs->go_debug > 0)
400 error_reporting(E_USER_WARNING|E_USER_ERROR|E_USER_NOTICE);
401 ini_set("display_errors", "On");
403 if ($SysPrefs->error_logfile != '') {
404 ini_set("error_log", $SysPrefs->error_logfile);
405 ini_set("ignore_repeated_errors", "On");
406 ini_set("log_errors", "On");
411 Uncomment the setting below when using FA on shared hosting
412 to avoid unexpeced session timeouts.
413 Make sure this directory exists and is writable!
415 // ini_set('session.save_path', dirname(__FILE__).'/../tmp/');
417 ini_set('session.gc_maxlifetime', 36000); // 10hrs
419 hook_session_start(@$_POST["company_login_name"]);
421 $Session_manager = new SessionManager();
422 $Session_manager->sessionStart('FA'.md5(dirname(__FILE__)));
424 // this is to fix the "back-do-you-want-to-refresh" issue - thanx PHPFreaks
425 header("Cache-control: private");
429 if ($SysPrefs->login_delay > 0)
430 @include_once($path_to_root . "/tmp/faillog.php");
432 // Page Initialisation
433 if (!isset($_SESSION['wa_current_user']) || !$_SESSION['wa_current_user']->logged_in()
434 || !isset($_SESSION['language']) || !method_exists($_SESSION['language'], 'set_language'))
436 $l = array_search_value($dflt_lang, $installed_languages, 'code');
437 $_SESSION['language'] = new language($l['name'], $l['code'], $l['encoding'],
438 (isset($l['rtl']) && $l['rtl'] === true) ? 'rtl' : 'ltr');
441 $_SESSION['language']->set_language($_SESSION['language']->code);
444 include_once($path_to_root . "/includes/access_levels.inc");
445 include_once($path_to_root . "/version.php");
446 include_once($path_to_root . "/includes/main.inc");
447 include_once($path_to_root . "/includes/app_entries.inc");
449 // Ajax communication object
452 // js/php validation rules container
454 // bindings for editors
456 // page help. Currently help for function keys.
459 $Refs = new references();
461 // intercept all output to destroy it in case of ajax call
462 register_shutdown_function('end_flush');
463 ob_start('output_html',0);
465 if (!isset($_SESSION["wa_current_user"]))
466 $_SESSION["wa_current_user"] = new current_user();
469 html_cleanup($_POST);
470 html_cleanup($_REQUEST);
471 html_cleanup($_SERVER);
473 // logout.php is the only page we should have always
474 // accessable regardless of access level and current login status.
475 if (!defined('FA_LOGOUT_PHP_FILE')){
479 if (!$_SESSION["wa_current_user"]->old_db)
480 include_once($path_to_root . '/company/'.user_company().'/installed_extensions.php');
484 if (!$_SESSION["wa_current_user"]->logged_in())
486 if (@$SysPrefs->allow_password_reset && !$SysPrefs->allow_demo_mode
487 && (isset($_GET['reset']) || isset($_POST['email_entry_field']))) {
488 if (!isset($_POST["email_entry_field"])) {
489 include($path_to_root . "/access/password_reset.php");
493 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
494 for ($i = 0; $i < count($db_connections); $i++) {
495 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
496 $_POST["company_login_name"] = $i;
497 unset($_POST["company_login_nickname"]);
498 break 1; // cannot pass variables to break from PHP v5.4 onwards
502 $_succeed = isset($db_connections[$_POST["company_login_name"]]) &&
503 $_SESSION["wa_current_user"]->reset_password($_POST["company_login_name"],
504 $_POST["email_entry_field"]);
507 password_reset_success();
510 password_reset_fail();
514 if (!isset($_POST["user_name_entry_field"]) or $_POST["user_name_entry_field"] == "")
516 // strip ajax marker from uri, to force synchronous page reload
517 $_SESSION['timeout'] = array( 'uri'=>preg_replace('/JsHttpRequest=(?:(\d+)-)?([^&]+)/s',
518 '', html_specials_encode($_SERVER['REQUEST_URI'])),
521 include($path_to_root . "/access/login.php");
523 $Ajax->activate('_page_body');
526 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
527 for ($i = 0; $i < count($db_connections); $i++) {
528 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
529 $_POST["company_login_name"] = $i;
530 unset($_POST["company_login_nickname"]);
531 break 1; // cannot pass variables to break from PHP v5.4 onwards
535 $succeed = isset($db_connections[$_POST["company_login_name"]]) &&
536 $_SESSION["wa_current_user"]->login($_POST["company_login_name"],
537 $_POST["user_name_entry_field"], $_POST["password"]);
538 // select full vs fallback ui mode on login
539 $_SESSION["wa_current_user"]->ui_mode = $_POST['ui_mode'];
542 // Incorrect password
545 elseif(isset($_SESSION['timeout']) && !$_SESSION['timeout']['post'])
547 // in case of GET request redirect to avoid confirmation dialog
548 // after return from menu option
549 header("HTTP/1.1 303 See Other");
550 header("Location: ".$_SESSION['timeout']['uri']);
553 $lang = &$_SESSION['language'];
554 $lang->set_language($_SESSION['language']->code);
557 { set_global_connection();
559 db_set_encoding($_SESSION['language']->encoding);
562 if (!isset($_SESSION["App"])) {
563 $_SESSION["App"] = new front_accounting();
564 $_SESSION["App"]->init();
569 // POST vars cleanup needed for direct reuse.
570 // We quote all values later with db_escape() before db update.
571 $_POST = strip_quotes($_POST);