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 ***********************************************************************/
12 define('VARLIB_PATH', $path_to_root.'/tmp');
13 define('VARLOG_PATH', $path_to_root.'/tmp');
14 define('SECURE_ONLY', true); // if you really need also http (unsecure) access allowed, you can set this to NULL
18 function sessionStart($name, $limit = 0, $path = '/', $domain = null, $secure = null)
20 // Set the cookie name
24 $https = isset($secure) ? $secure : (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
26 // Set session cookie options
27 if (version_compare(PHP_VERSION, '5.2', '<')) // avoid failure on older php versions
28 session_set_cookie_params($limit, $path, $domain, $https);
30 session_set_cookie_params($limit, $path, $domain, $https, true);
34 // Make sure the session hasn't expired, and destroy it if it has
35 if ($this->validateSession())
37 // Check to see if the session is new or a hijacking attempt
38 if(!$this->preventHijacking())
40 // Reset session data and regenerate id
42 $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
43 $_SESSION['userAgent'] = @$_SERVER['HTTP_USER_AGENT'];
44 $this->regenerateSession();
46 // Give a 5% chance of the session id changing on any request
48 elseif (rand(1, 100) <= 5)
50 $this->regenerateSession();
61 function preventHijacking()
63 if (!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
66 if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
69 if ( $_SESSION['userAgent'] != @$_SERVER['HTTP_USER_AGENT'])
75 function regenerateSession()
77 // If this session is obsolete it means there already is a new id
78 if (isset($_SESSION['OBSOLETE']) && ($_SESSION['OBSOLETE'] == true))
81 // Set current session to expire in 10 seconds
82 $_SESSION['OBSOLETE'] = true;
83 $_SESSION['EXPIRES'] = time() + 10;
85 // Create new session without destroying the old one
86 session_regenerate_id();
87 // Grab current session ID and close both sessions to allow other scripts to use them
88 $newSession = session_id();
89 session_write_close();
90 // Set session ID to the new one, and start it back up again
92 session_id($newSession);
95 // Now we unset the obsolete and expiration values for the session we want to keep
96 unset($_SESSION['OBSOLETE']);
97 unset($_SESSION['EXPIRES']);
100 function validateSession()
102 if (isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES']) )
105 if (isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time())
112 function output_html($text)
114 global $before_box, $Ajax, $messages;
115 // Fatal errors are not send to error_handler,
116 // so we must check the output
117 if ($text && preg_match('/\bFatal error(<.*?>)?:(.*)/i', $text, $m)) {
118 $Ajax->aCommands = array(); // Don't update page via ajax on errors
119 $text = preg_replace('/\bFatal error(<.*?>)?:(.*)/i','', $text);
120 $messages[] = array(E_ERROR, $m[2], null, null);
123 return in_ajax() ? fmt_errors() : ($before_box.fmt_errors().$text);
125 //----------------------------------------------------------------------------------------
127 function kill_login()
132 //----------------------------------------------------------------------------------------
134 function login_fail()
136 global $path_to_root;
138 header("HTTP/1.1 401 Authorization Required");
139 echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Password") . "<b></font><br><br>";
140 echo "<b>" . _("The user and password combination is not valid for the system.") . "<b><br><br>";
141 echo _("If you are not an authorized user, please contact your system administrator to obtain an account to enable you to use the system.");
142 echo "<br><a href='$path_to_root/index.php'>" . _("Try again") . "</a>";
148 function password_reset_fail()
150 global $path_to_root;
152 echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Email") . "<b></font><br><br>";
153 echo "<b>" . _("The email address does not exist in the system, or is used by more than one user.") . "<b><br><br>";
155 echo _("Plase try again or contact your system administrator to obtain new password.");
156 echo "<br><a href='$path_to_root/index.php?reset=1'>" . _("Try again") . "</a>";
163 function password_reset_success()
165 global $path_to_root;
167 echo "<center><br><br><font size='5' color='green'><b>" . _("New password sent") . "<b></font><br><br>";
168 echo "<b>" . _("A new password has been sent to your mailbox.") . "<b><br><br>";
170 echo "<br><a href='$path_to_root/index.php'>" . _("Login here") . "</a>";
177 function check_faillog()
179 global $SysPrefs, $login_faillog;
181 $user = $_SESSION["wa_current_user"]->user;
183 $_SESSION["wa_current_user"]->login_attempt++;
184 if (@$SysPrefs->login_delay && (@$login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$SysPrefs->login_max_attempts) && (time() < $login_faillog[$user]['last'] + $SysPrefs->login_delay))
191 Ensure file is re-read on next request if php caching is active
193 function cache_invalidate($filename)
195 if (function_exists('opcache_invalidate')) // OpCode extension
196 opcache_invalidate($filename);
200 Simple brute force attack detection is performed before connection to company database is open. Therefore access counters have to be stored in file.
201 Login attempts counter is created for every new user IP, which partialy prevent DOS attacks.
203 function write_login_filelog($login, $result)
205 global $login_faillog, $SysPrefs, $path_to_root;
207 $user = $_SESSION["wa_current_user"]->user;
209 $ip = $_SERVER['REMOTE_ADDR'];
211 if (!isset($login_faillog[$user][$ip]) || $result) // init or reset on successfull login
212 $login_faillog[$user] = array($ip => 0, 'last' => '');
216 if ($login_faillog[$user][$ip] < @$SysPrefs->login_max_attempts) {
218 $login_faillog[$user][$ip]++;
220 $login_faillog[$user][$ip] = 0; // comment out to restart counter only after successfull login.
221 error_log(sprintf(_("Brute force attack on account '%s' detected. Access for non-logged users temporarily blocked." ), $login));
223 $login_faillog[$user]['last'] = time();
228 $msg .= "Login attempts info.\n";
230 $msg .= "\$login_faillog = " .var_export($login_faillog, true). ";\n";
232 $filename = VARLIB_PATH."/faillog.php";
234 if ((!file_exists($filename) && is_writable(VARLIB_PATH)) || is_writable($filename))
236 file_put_contents($filename, $msg);
237 cache_invalidate($filename);
241 //----------------------------------------------------------------------------------------
243 function check_page_security($page_security)
249 if (!$_SESSION["wa_current_user"]->check_user_access())
251 // notification after upgrade from pre-2.2 version
252 $msg = $_SESSION["wa_current_user"]->old_db ?
253 _("Security settings have not been defined for your user account.")
254 . "<br>" . _("Please contact your system administrator.")
255 : _("Please remove \$security_groups and \$security_headings arrays from config.php file!");
256 } elseif (!$SysPrefs->db_ok && !$_SESSION["wa_current_user"]->can_access('SA_SOFTWAREUPGRADE'))
258 $msg = _('Access to application has been blocked until database upgrade is completed by system administrator.');
263 end_page(@$_REQUEST['popup']);
268 if (!$_SESSION["wa_current_user"]->can_access_page($page_security))
271 echo "<center><br><br><br><b>";
272 echo _("The security settings on your account do not permit you to access this function");
274 echo "<br><br><br><br></center>";
275 end_page(@$_REQUEST['popup']);
278 if (!$SysPrefs->db_ok
279 && !in_array($page_security, array('SA_SOFTWAREUPGRADE', 'SA_OPEN', 'SA_BACKUP')))
281 display_error(_('System is blocked after source upgrade until database is updated on System/Software Upgrade page'));
288 Helper function for setting page security level depeding on
289 GET start variable and/or some value stored in session variable.
290 Before the call $page_security should be set to default page_security value.
292 function set_page_security($value=null, $trans = array(), $gtrans = array())
294 global $page_security;
296 // first check is this is not start page call
297 foreach($gtrans as $key => $area)
298 if (isset($_GET[$key])) {
299 $page_security = $area;
303 // then check session value
304 if (isset($trans[$value])) {
305 $page_security = $trans[$value];
310 //-----------------------------------------------------------------------------
311 // Removing magic quotes from nested arrays/variables
313 function strip_quotes($data)
315 if(version_compare(phpversion(), '5.4', '<') && get_magic_quotes_gpc()) {
316 if(is_array($data)) {
317 foreach($data as $k => $v) {
318 $data[$k] = strip_quotes($data[$k]);
321 return stripslashes($data);
327 htmlspecialchars does not support certain encodings.
328 ISO-8859-2 fortunately has the same special characters positions as
329 ISO-8859-1, so fix is easy. If any other unsupported encoding is used,
332 function html_specials_encode($str)
334 return htmlspecialchars($str, ENT_QUOTES, $_SESSION['language']->encoding=='iso-8859-2' ?
335 'ISO-8859-1' : $_SESSION['language']->encoding);
338 function html_cleanup(&$parms)
340 foreach($parms as $name => $value) {
341 if (is_array($value))
342 html_cleanup($parms[$name]);
344 $parms[$name] = html_specials_encode($value);
346 reset($parms); // needed for direct key() usage later throughout the sources
349 //============================================================================
352 function login_timeout()
354 // skip timeout on logout page
355 if ($_SESSION["wa_current_user"]->logged) {
356 $tout = $_SESSION["wa_current_user"]->timeout;
357 if ($tout && (time() > $_SESSION["wa_current_user"]->last_act + $tout))
359 $_SESSION["wa_current_user"]->logged = false;
361 $_SESSION["wa_current_user"]->last_act = time();
364 //============================================================================
365 if (!isset($path_to_root))
370 // Prevent register_globals vulnerability
371 if (isset($_GET['path_to_root']) || isset($_POST['path_to_root']))
372 die("Restricted access");
374 include_once($path_to_root . "/includes/errors.inc");
375 // colect all error msgs
376 set_error_handler('error_handler' /*, errtypes */);
377 set_exception_handler('exception_handler');
379 include_once($path_to_root . "/includes/current_user.inc");
380 include_once($path_to_root . "/frontaccounting.php");
381 include_once($path_to_root . "/admin/db/security_db.inc");
382 include_once($path_to_root . "/includes/lang/language.inc");
383 include_once($path_to_root . "/config_db.php");
384 include_once($path_to_root . "/includes/ajax.inc");
385 include_once($path_to_root . "/includes/ui/ui_msgs.inc");
386 include_once($path_to_root . "/includes/prefs/sysprefs.inc");
388 include_once($path_to_root . "/includes/hooks.inc");
390 // include all extensions hook files.
392 foreach ($installed_extensions as $ext)
394 if (file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
395 include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
398 ini_set('session.gc_maxlifetime', 36000); // moved from below.
400 $Session_manager = new SessionManager();
401 $Session_manager->sessionStart('FA'.md5(dirname(__FILE__)), 0, '/', null, SECURE_ONLY);
403 $_SESSION['SysPrefs'] = new sys_prefs();
405 $SysPrefs = &$_SESSION['SysPrefs'];
407 //----------------------------------------------------------------------------------------
408 // set to reasonable values if not set in config file (pre-2.3.12 installations)
410 if ((!isset($SysPrefs->login_delay)) || ($SysPrefs->login_delay < 0))
411 $SysPrefs->login_delay = 10;
413 if ((!isset($SysPrefs->login_max_attempts)) || ($SysPrefs->login_max_attempts < 0))
414 $SysPrefs->login_max_attempts = 3;
416 if ($SysPrefs->go_debug > 0)
417 $cur_error_level = -1;
419 $cur_error_level = E_USER_WARNING|E_USER_ERROR|E_USER_NOTICE;
421 error_reporting($cur_error_level);
422 ini_set("display_errors", "On");
424 if ($SysPrefs->error_logfile != '') {
425 ini_set("error_log", $SysPrefs->error_logfile);
426 ini_set("ignore_repeated_errors", "On");
427 ini_set("log_errors", "On");
431 Uncomment the setting below when using FA on shared hosting
432 to avoid unexpeced session timeouts.
433 Make sure this directory exists and is writable!
435 // ini_set('session.save_path', VARLIB_PATH.'/');
437 // ini_set('session.gc_maxlifetime', 36000); // 10hrs - moved to before session_manager
439 hook_session_start(@$_POST["company_login_name"]);
441 // this is to fix the "back-do-you-want-to-refresh" issue - thanx PHPFreaks
442 header("Cache-control: private");
446 if ($SysPrefs->login_delay > 0 && file_exists(VARLIB_PATH."/faillog.php"))
447 include_once(VARLIB_PATH."/faillog.php");
449 // Page Initialisation
450 if (isset($dflt_lang) && isset($installed_languages))
452 if (!isset($_SESSION['wa_current_user']) || !$_SESSION['wa_current_user']->logged_in()
453 || !isset($_SESSION['language']) || !method_exists($_SESSION['language'], 'set_language'))
455 $l = array_search_value($dflt_lang, $installed_languages, 'code');
456 $_SESSION['language'] = new language($l['name'], $l['code'], $l['encoding'],
457 (isset($l['rtl']) && $l['rtl'] === true) ? 'rtl' : 'ltr');
460 $_SESSION['language']->set_language($_SESSION['language']->code);
463 include_once($path_to_root . "/includes/access_levels.inc");
464 include_once($path_to_root . "/version.php");
465 include_once($path_to_root . "/includes/main.inc");
466 include_once($path_to_root . "/includes/app_entries.inc");
468 // Ajax communication object
471 // js/php validation rules container
473 // bindings for editors
475 // page help. Currently help for function keys.
478 $Refs = new references();
480 // intercept all output to destroy it in case of ajax call
481 register_shutdown_function('end_flush');
482 ob_start('output_html',0);
484 if (!isset($_SESSION["wa_current_user"]))
485 $_SESSION["wa_current_user"] = new current_user();
488 html_cleanup($_POST);
489 html_cleanup($_REQUEST);
490 html_cleanup($_SERVER);
492 // logout.php is the only page we should have always
493 // accessable regardless of access level and current login status.
494 if (!defined('FA_LOGOUT_PHP_FILE')){
498 if (!$_SESSION["wa_current_user"]->old_db && file_exists($path_to_root . '/company/'.user_company().'/installed_extensions.php'))
499 include($path_to_root . '/company/'.user_company().'/installed_extensions.php');
503 if (!$_SESSION["wa_current_user"]->logged_in())
505 if (@$SysPrefs->allow_password_reset && !$SysPrefs->allow_demo_mode
506 && (isset($_GET['reset']) || isset($_POST['email_entry_field']))) {
507 if (!isset($_POST["email_entry_field"])) {
508 include($path_to_root . "/access/password_reset.php");
512 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
513 for ($i = 0; $i < count($db_connections); $i++) {
514 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
515 $_POST["company_login_name"] = $i;
516 unset($_POST["company_login_nickname"]);
517 break 1; // cannot pass variables to break from PHP v5.4 onwards
521 $_succeed = isset($db_connections[$_POST["company_login_name"]]) &&
522 $_SESSION["wa_current_user"]->reset_password($_POST["company_login_name"],
523 $_POST["email_entry_field"]);
526 password_reset_success();
529 password_reset_fail();
533 if (!isset($_POST["user_name_entry_field"]) or $_POST["user_name_entry_field"] == "")
535 // strip ajax marker from uri, to force synchronous page reload
536 $_SESSION['timeout'] = array( 'uri'=>preg_replace('/JsHttpRequest=(?:(\d+)-)?([^&]+)/s',
537 '', html_specials_encode($_SERVER['REQUEST_URI'])),
540 $Ajax->popup($path_to_root ."/access/timeout.php");
542 include($path_to_root . "/access/login.php");
545 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
546 for ($i = 0; $i < count($db_connections); $i++) {
547 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
548 $_POST["company_login_name"] = $i;
549 unset($_POST["company_login_nickname"]);
550 break 1; // cannot pass variables to break from PHP v5.4 onwards
554 $succeed = isset($db_connections[$_POST["company_login_name"]]) &&
555 $_SESSION["wa_current_user"]->login($_POST["company_login_name"],
556 $_POST["user_name_entry_field"], $_POST["password"]);
557 // select full vs fallback ui mode on login
558 $_SESSION["wa_current_user"]->ui_mode = $_POST['ui_mode'];
561 // Incorrect password
562 if (isset($_SESSION['timeout'])) {
563 include($path_to_root . "/access/login.php");
568 elseif(isset($_SESSION['timeout']) && !$_SESSION['timeout']['post'])
570 // in case of GET request redirect to avoid confirmation dialog
571 // after return from menu option
572 header("HTTP/1.1 307 Temporary Redirect");
573 header("Location: ".$_SESSION['timeout']['uri']);
576 $lang = &$_SESSION['language'];
577 $lang->set_language($_SESSION['language']->code);
581 set_global_connection();
584 db_set_encoding($_SESSION['language']->encoding);
586 $SysPrefs->refresh();
588 if (!isset($_SESSION["App"])) {
589 $_SESSION["App"] = new front_accounting();
590 $_SESSION["App"]->init();
594 // POST vars cleanup needed for direct reuse.
595 // We quote all values later with db_escape() before db update.
596 $_POST = strip_quotes($_POST);