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');
17 function sessionStart($name, $limit = 0, $path = '/', $domain = null, $secure = null)
19 // Set the cookie name
23 $https = isset($secure) ? $secure : (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
25 // Set session cookie options
26 if (version_compare(PHP_VERSION, '5.2', '<')) // avoid failure on older php versions
27 session_set_cookie_params($limit, $path, $domain, $https);
29 session_set_cookie_params($limit, $path, $domain, $https, true);
33 // Make sure the session hasn't expired, and destroy it if it has
34 if ($this->validateSession())
36 // Check to see if the session is new or a hijacking attempt
37 if(!$this->preventHijacking())
39 // Reset session data and regenerate id
41 $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
42 $_SESSION['userAgent'] = @$_SERVER['HTTP_USER_AGENT'];
43 $this->regenerateSession();
45 // Give a 5% chance of the session id changing on any request
47 elseif (rand(1, 100) <= 5)
49 $this->regenerateSession();
60 function preventHijacking()
62 if (!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
65 if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
68 if ( $_SESSION['userAgent'] != @$_SERVER['HTTP_USER_AGENT'])
74 function regenerateSession()
76 // If this session is obsolete it means there already is a new id
77 if (isset($_SESSION['OBSOLETE']) && ($_SESSION['OBSOLETE'] == true))
80 // Set current session to expire in 10 seconds
81 $_SESSION['OBSOLETE'] = true;
82 $_SESSION['EXPIRES'] = time() + 10;
84 // Create new session without destroying the old one
85 session_regenerate_id();
86 // Grab current session ID and close both sessions to allow other scripts to use them
87 $newSession = session_id();
88 session_write_close();
89 // Set session ID to the new one, and start it back up again
91 session_id($newSession);
94 // Now we unset the obsolete and expiration values for the session we want to keep
95 unset($_SESSION['OBSOLETE']);
96 unset($_SESSION['EXPIRES']);
99 function validateSession()
101 if (isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES']) )
104 if (isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time())
111 function output_html($text)
113 global $before_box, $Ajax, $messages;
114 // Fatal errors are not send to error_handler,
115 // so we must check the output
116 if ($text && preg_match('/\bFatal error(<.*?>)?:(.*)/i', $text, $m)) {
117 $Ajax->aCommands = array(); // Don't update page via ajax on errors
118 $text = preg_replace('/\bFatal error(<.*?>)?:(.*)/i','', $text);
119 $messages[] = array(E_ERROR, $m[2], null, null);
122 return in_ajax() ? fmt_errors() : ($before_box.fmt_errors().$text);
124 //----------------------------------------------------------------------------------------
126 function kill_login()
131 //----------------------------------------------------------------------------------------
133 function login_fail()
135 global $path_to_root;
137 header("HTTP/1.1 401 Authorization Required");
138 echo "<center><br><br><font size='5' color='red'><b>" . _("Incorrect Password") . "<b></font><br><br>";
139 echo "<b>" . _("The user and password combination is not valid for the system.") . "<b><br><br>";
140 echo _("If you are not an authorized user, please contact your system administrator to obtain an account to enable you to use the system.");
141 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, or is used by more than one user.") . "<b><br><br>";
154 echo _("Plase try again or contact your system administrator to obtain new password.");
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 $_SESSION["wa_current_user"]->login_attempt++;
183 if (@$SysPrefs->login_delay && (@$login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$SysPrefs->login_max_attempts) && (time() < $login_faillog[$user]['last'] + $SysPrefs->login_delay))
190 Ensure file is re-read on next request if php caching is active
192 function cache_invalidate($filename)
194 if (function_exists('opcache_invalidate')) // OpCode extension
195 opcache_invalidate($filename);
199 Simple brute force attack detection is performed before connection to company database is open. Therefore access counters have to be stored in file.
200 Login attempts counter is created for every new user IP, which partialy prevent DOS attacks.
202 function write_login_filelog($login, $result)
204 global $login_faillog, $SysPrefs, $path_to_root;
206 $user = $_SESSION["wa_current_user"]->user;
208 $ip = $_SERVER['REMOTE_ADDR'];
210 if (!isset($login_faillog[$user][$ip]) || $result) // init or reset on successfull login
211 $login_faillog[$user] = array($ip => 0, 'last' => '');
215 if ($login_faillog[$user][$ip] < @$SysPrefs->login_max_attempts) {
217 $login_faillog[$user][$ip]++;
219 $login_faillog[$user][$ip] = 0; // comment out to restart counter only after successfull login.
220 error_log(sprintf(_("Brute force attack on account '%s' detected. Access for non-logged users temporarily blocked." ), $login));
222 $login_faillog[$user]['last'] = time();
227 $msg .= "Login attempts info.\n";
229 $msg .= "\$login_faillog = " .var_export($login_faillog, true). ";\n";
231 $filename = VARLIB_PATH."/faillog.php";
233 if ((!file_exists($filename) && is_writable(VARLIB_PATH)) || is_writable($filename))
235 file_put_contents($filename, $msg);
236 cache_invalidate($filename);
240 //----------------------------------------------------------------------------------------
242 function check_page_security($page_security)
248 if (!$_SESSION["wa_current_user"]->check_user_access())
250 // notification after upgrade from pre-2.2 version
251 $msg = $_SESSION["wa_current_user"]->old_db ?
252 _("Security settings have not been defined for your user account.")
253 . "<br>" . _("Please contact your system administrator.")
254 : _("Please remove \$security_groups and \$security_headings arrays from config.php file!");
255 } elseif (!$SysPrefs->db_ok && !$_SESSION["wa_current_user"]->can_access('SA_SOFTWAREUPGRADE'))
257 $msg = _('Access to application has been blocked until database upgrade is completed by system administrator.');
262 end_page(@$_REQUEST['popup']);
267 if (!$_SESSION["wa_current_user"]->can_access_page($page_security))
270 echo "<center><br><br><br><b>";
271 echo _("The security settings on your account do not permit you to access this function");
273 echo "<br><br><br><br></center>";
274 end_page(@$_REQUEST['popup']);
277 if (!$SysPrefs->db_ok
278 && !in_array($page_security, array('SA_SOFTWAREUPGRADE', 'SA_OPEN', 'SA_BACKUP')))
280 display_error(_('System is blocked after source upgrade until database is updated on System/Software Upgrade page'));
287 Helper function for setting page security level depeding on
288 GET start variable and/or some value stored in session variable.
289 Before the call $page_security should be set to default page_security value.
291 function set_page_security($value=null, $trans = array(), $gtrans = array())
293 global $page_security;
295 // first check is this is not start page call
296 foreach($gtrans as $key => $area)
297 if (isset($_GET[$key])) {
298 $page_security = $area;
302 // then check session value
303 if (isset($trans[$value])) {
304 $page_security = $trans[$value];
309 //-----------------------------------------------------------------------------
310 // Removing magic quotes from nested arrays/variables
312 function strip_quotes($data)
314 if(version_compare(phpversion(), '5.4', '<') && get_magic_quotes_gpc()) {
315 if(is_array($data)) {
316 foreach($data as $k => $v) {
317 $data[$k] = strip_quotes($data[$k]);
320 return stripslashes($data);
326 htmlspecialchars does not support certain encodings.
327 ISO-8859-2 fortunately has the same special characters positions as
328 ISO-8859-1, so fix is easy. If any other unsupported encoding is used,
331 function html_specials_encode($str)
333 return htmlspecialchars($str, ENT_QUOTES, $_SESSION['language']->encoding=='iso-8859-2' ?
334 'ISO-8859-1' : $_SESSION['language']->encoding);
337 function html_cleanup(&$parms)
339 foreach($parms as $name => $value) {
340 if (is_array($value))
341 html_cleanup($parms[$name]);
343 $parms[$name] = html_specials_encode($value);
345 reset($parms); // needed for direct key() usage later throughout the sources
348 //============================================================================
351 function login_timeout()
353 // skip timeout on logout page
354 if ($_SESSION["wa_current_user"]->logged) {
355 $tout = $_SESSION["wa_current_user"]->timeout;
356 if ($tout && (time() > $_SESSION["wa_current_user"]->last_act + $tout))
358 $_SESSION["wa_current_user"]->logged = false;
360 $_SESSION["wa_current_user"]->last_act = time();
363 //============================================================================
364 if (!isset($path_to_root))
369 // Prevent register_globals vulnerability
370 if (isset($_GET['path_to_root']) || isset($_POST['path_to_root']))
371 die("Restricted access");
373 include_once($path_to_root . "/includes/errors.inc");
374 // colect all error msgs
375 set_error_handler('error_handler' /*, errtypes */);
376 set_exception_handler('exception_handler');
378 include_once($path_to_root . "/includes/current_user.inc");
379 include_once($path_to_root . "/frontaccounting.php");
380 include_once($path_to_root . "/admin/db/security_db.inc");
381 include_once($path_to_root . "/includes/lang/language.inc");
382 include_once($path_to_root . "/config_db.php");
383 include_once($path_to_root . "/includes/ajax.inc");
384 include_once($path_to_root . "/includes/ui/ui_msgs.inc");
385 include_once($path_to_root . "/includes/prefs/sysprefs.inc");
387 include_once($path_to_root . "/includes/hooks.inc");
389 // include all extensions hook files.
391 foreach ($installed_extensions as $ext)
393 if (file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
394 include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
397 ini_set('session.gc_maxlifetime', 36000); // moved from below.
399 $Session_manager = new SessionManager();
400 $Session_manager->sessionStart('FA'.md5(dirname(__FILE__)));
402 $_SESSION['SysPrefs'] = new sys_prefs();
404 $SysPrefs = &$_SESSION['SysPrefs'];
406 //----------------------------------------------------------------------------------------
407 // set to reasonable values if not set in config file (pre-2.3.12 installations)
409 if ((!isset($SysPrefs->login_delay)) || ($SysPrefs->login_delay < 0))
410 $SysPrefs->login_delay = 10;
412 if ((!isset($SysPrefs->login_max_attempts)) || ($SysPrefs->login_max_attempts < 0))
413 $SysPrefs->login_max_attempts = 3;
415 if ($SysPrefs->go_debug > 0)
416 $cur_error_level = -1;
418 $cur_error_level = E_USER_WARNING|E_USER_ERROR|E_USER_NOTICE;
420 error_reporting($cur_error_level);
421 ini_set("display_errors", "On");
423 if ($SysPrefs->error_logfile != '') {
424 ini_set("error_log", $SysPrefs->error_logfile);
425 ini_set("ignore_repeated_errors", "On");
426 ini_set("log_errors", "On");
430 Uncomment the setting below when using FA on shared hosting
431 to avoid unexpeced session timeouts.
432 Make sure this directory exists and is writable!
434 // ini_set('session.save_path', VARLIB_PATH.'/');
436 // ini_set('session.gc_maxlifetime', 36000); // 10hrs - moved to before session_manager
438 hook_session_start(@$_POST["company_login_name"]);
440 // this is to fix the "back-do-you-want-to-refresh" issue - thanx PHPFreaks
441 header("Cache-control: private");
445 if ($SysPrefs->login_delay > 0 && file_exists(VARLIB_PATH."/faillog.php"))
446 include_once(VARLIB_PATH."/faillog.php");
448 // Page Initialisation
449 if (!isset($_SESSION['wa_current_user']) || !$_SESSION['wa_current_user']->logged_in()
450 || !isset($_SESSION['language']) || !method_exists($_SESSION['language'], 'set_language'))
452 $l = array_search_value($dflt_lang, $installed_languages, 'code');
453 $_SESSION['language'] = new language($l['name'], $l['code'], $l['encoding'],
454 (isset($l['rtl']) && $l['rtl'] === true) ? 'rtl' : 'ltr');
457 $_SESSION['language']->set_language($_SESSION['language']->code);
460 include_once($path_to_root . "/includes/access_levels.inc");
461 include_once($path_to_root . "/version.php");
462 include_once($path_to_root . "/includes/main.inc");
463 include_once($path_to_root . "/includes/app_entries.inc");
465 // Ajax communication object
468 // js/php validation rules container
470 // bindings for editors
472 // page help. Currently help for function keys.
475 $Refs = new references();
477 // intercept all output to destroy it in case of ajax call
478 register_shutdown_function('end_flush');
479 ob_start('output_html',0);
481 if (!isset($_SESSION["wa_current_user"]))
482 $_SESSION["wa_current_user"] = new current_user();
485 html_cleanup($_POST);
486 html_cleanup($_REQUEST);
487 html_cleanup($_SERVER);
489 // logout.php is the only page we should have always
490 // accessable regardless of access level and current login status.
491 if (!defined('FA_LOGOUT_PHP_FILE')){
495 if (!$_SESSION["wa_current_user"]->old_db && file_exists($path_to_root . '/company/'.user_company().'/installed_extensions.php'))
496 include($path_to_root . '/company/'.user_company().'/installed_extensions.php');
500 if (!$_SESSION["wa_current_user"]->logged_in())
502 if (@$SysPrefs->allow_password_reset && !$SysPrefs->allow_demo_mode
503 && (isset($_GET['reset']) || isset($_POST['email_entry_field']))) {
504 if (!isset($_POST["email_entry_field"])) {
505 include($path_to_root . "/access/password_reset.php");
509 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
510 for ($i = 0; $i < count($db_connections); $i++) {
511 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
512 $_POST["company_login_name"] = $i;
513 unset($_POST["company_login_nickname"]);
514 break 1; // cannot pass variables to break from PHP v5.4 onwards
518 $_succeed = isset($db_connections[$_POST["company_login_name"]]) &&
519 $_SESSION["wa_current_user"]->reset_password($_POST["company_login_name"],
520 $_POST["email_entry_field"]);
523 password_reset_success();
526 password_reset_fail();
530 if (!isset($_POST["user_name_entry_field"]) or $_POST["user_name_entry_field"] == "")
532 // strip ajax marker from uri, to force synchronous page reload
533 $_SESSION['timeout'] = array( 'uri'=>preg_replace('/JsHttpRequest=(?:(\d+)-)?([^&]+)/s',
534 '', html_specials_encode($_SERVER['REQUEST_URI'])),
537 $Ajax->popup($path_to_root ."/access/timeout.php");
539 include($path_to_root . "/access/login.php");
542 if (isset($_POST["company_login_nickname"]) && !isset($_POST["company_login_name"])) {
543 for ($i = 0; $i < count($db_connections); $i++) {
544 if ($db_connections[$i]["name"] == $_POST["company_login_nickname"]) {
545 $_POST["company_login_name"] = $i;
546 unset($_POST["company_login_nickname"]);
547 break 1; // cannot pass variables to break from PHP v5.4 onwards
551 $succeed = isset($db_connections[$_POST["company_login_name"]]) &&
552 $_SESSION["wa_current_user"]->login($_POST["company_login_name"],
553 $_POST["user_name_entry_field"], $_POST["password"]);
554 // select full vs fallback ui mode on login
555 $_SESSION["wa_current_user"]->ui_mode = $_POST['ui_mode'];
558 // Incorrect password
559 if (isset($_SESSION['timeout'])) {
560 include($path_to_root . "/access/login.php");
565 elseif(isset($_SESSION['timeout']) && !$_SESSION['timeout']['post'])
567 // in case of GET request redirect to avoid confirmation dialog
568 // after return from menu option
569 header("HTTP/1.1 307 Temporary Redirect");
570 header("Location: ".$_SESSION['timeout']['uri']);
573 $lang = &$_SESSION['language'];
574 $lang->set_language($_SESSION['language']->code);
578 set_global_connection();
581 db_set_encoding($_SESSION['language']->encoding);
583 $SysPrefs->refresh();
585 if (!isset($_SESSION["App"])) {
586 $_SESSION["App"] = new front_accounting();
587 $_SESSION["App"]->init();
591 // POST vars cleanup needed for direct reuse.
592 // We quote all values later with db_escape() before db update.
593 $_POST = strip_quotes($_POST);