Index: html/includes/authenticate.inc.php
===================================================================
--- html/includes/authenticate.inc.php	(revision 10125)
+++ html/includes/authenticate.inc.php	(working copy)
@@ -78,32 +78,12 @@
 //register_shutdown_function('session_commit'); // This already done at end of script run
 if (!session_is_active())
 {
-  //logfile('debug_auth.log', 'session_start() called at '.$currenttime);
-  session_commit(); // Write and close current session
+  session_regenerate_new();
+}
 
-  //session_start(); // session starts in session_regenerate too!
-  session_regenerate(); // Note, use this function after session_start() and before next session_commit()!
-  /*
-  if (isset($_SESSION['starttime']))
-  {
-    if ($currenttime - $_SESSION['starttime'] >= $lifetime_id && !is_graph())
-    {
-      logfile('debug_auth.log', 'session_regenerate_id() called at '.$currenttime);
-      // ID Lifetime expired, regenerate
-      session_regenerate_id(TRUE);
-      // Clean cache from _SESSION first, this cache used in ajax calls
-      if (isset($_SESSION['cache'])) { unset($_SESSION['cache']); }
-      $_SESSION['starttime'] = $currenttime;
-    }
-  } else {
-    $_SESSION['starttime']   = $currenttime;
-  }
-  */
-
-  //if (!is_graph())
-  //{
-  //  print_vars($vars); print_vars($_SESSION); print_vars($_COOKIE);
-  //}
+if($debug_auth && empty($_SESSION['authenticated']))
+{
+  logfile('debug_auth.log', __LINE__ . ' NOT Authenticated!!! ' . json_encode($_SESSION));
 }
 
 // Fallback to MySQL auth as default - FIXME do this in sqlconfig file?
@@ -404,7 +384,7 @@
 ///r($_COOKIE);
 ///r($permissions);
 
-//logfile('debug_auth.log', 'auth session_commit() called at '.time());
+//logfile('debug_auth.log', __LINE__ . ' ' . 'auth session_commit() called at '.time());
 //session_commit(); // Write and unblock current session (use session_set_var() and session_unset_var() for write to session variables!)
 
 /* Session manager specific functions */
@@ -438,7 +418,7 @@
   // Less entropy than HTTP_ACCEPT, but stable!
   $id .= $_SERVER['HTTP_ACCEPT_ENCODING'];
   $id .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
-  
+
   if ($config['web_session_ip'])
   {
     $remote_addr = get_remote_addr($config['web_session_ip_by_header']); // Remote address by header if configured
@@ -451,8 +431,8 @@
       {
         if ($debug_auth)
         {
-          logfile('debug_auth.log', "IP changed. IP=[$remote_addr]. ID=[$id]. URL=[" . $_SERVER['REQUEST_URI'] . "]");
-          logfile('debug_auth.log', var_export($_SESSION, TRUE));
+          logfile('debug_auth.log', __LINE__ . ' ' . "IP changed. IP=[$remote_addr]. ID=[$id]. URL=[" . $_SERVER['REQUEST_URI'] . "]");
+          logfile('debug_auth.log', __LINE__ . ' ' . var_export($_SESSION, TRUE));
         }
         unset($_SESSION['authenticated'],
               $_SESSION['user_id'],
@@ -478,8 +458,8 @@
     {
       if ($debug_auth)
       {
-        logfile('debug_auth.log', "UA changed. IP=[" . get_remote_addr($config['web_session_ip_by_header']) . "]. ID=[$id]. URL=[" . $_SERVER['REQUEST_URI'] . "]");
-        logfile('debug_auth.log', var_export($_SESSION, TRUE));
+        logfile('debug_auth.log', __LINE__ . ' ' . "UA changed. IP=[" . get_remote_addr($config['web_session_ip_by_header']) . "]. ID=[$id]. URL=[" . $_SERVER['REQUEST_URI'] . "]");
+        logfile('debug_auth.log', __LINE__ . ' ' . var_export($_SESSION, TRUE));
       }
       session_logout();
       reauth_with_message('Browser has changed.');
@@ -486,7 +466,7 @@
     }
     //if ($debug_auth)
     //{
-    //  logfile('debug_auth.log', "IP=[".get_remote_addr($config['web_session_ip_by_header'])."]. ID=[$id]. URL=[".$_SERVER['REQUEST_URI']."]");
+    //  logfile('debug_auth.log', __LINE__ . ' ' . "IP=[".get_remote_addr($config['web_session_ip_by_header'])."]. ID=[$id]. URL=[".$_SERVER['REQUEST_URI']."]");
     //}
   }
 
@@ -499,7 +479,7 @@
     $debug_log_array = array($user_unique_id, $remote_addr, $_SERVER['HTTP_USER_AGENT'],
                              $_SERVER['HTTP_ACCEPT_ENCODING'], $_SERVER['HTTP_ACCEPT_LANGUAGE'],
                              $_COOKIE['OBSID']);
-    logfile('debug_auth.log', json_encode($debug_log_array));
+    logfile('debug_auth.log', __LINE__ . ' ' . json_encode($debug_log_array));
   }
 
   //print_vars($id);
@@ -569,6 +549,7 @@
   {
     $remote_addr = $remote_addr_header . ' (' . $remote_addr . ')';
   }
+
   dbInsert(array('user'       => $_SESSION['username'],
                  'address'    => $remote_addr,
                  'user_agent' => $_SERVER['HTTP_USER_AGENT'],
@@ -628,38 +609,107 @@
  * Note, use this function after session_start() and before next session_commit()!
  *
  * @param int $lifetime_id Time in seconds for next regenerate session ID (default 30 min)
+ *
+ * Code borrowed from https://www.php.net/manual/en/function.session-regenerate-id.php
+ *
  */
-function session_regenerate($lifetime_id = 1800)
-{
-  session_start();
+function session_regenerate_new($lifetime_id = 1800) {
+    session_start();
 
-  $currenttime   = time();
-  if ($lifetime_id != 1800 && is_numeric($lifetime_id) && $lifetime_id >= 300)
-  {
-    $lifetime_id = intval($lifetime_id);
-  } else {
-    $lifetime_id = 1800;
-  }
-
-  if (isset($_SESSION['starttime']))
-  {
-    if ($currenttime - $_SESSION['starttime'] >= $lifetime_id && !is_graph())
+    if (isset($_SESSION['destroyed']))
     {
-      //logfile('debug_auth.log', 'session_regenerate_id() called at '.$currenttime);
-      // ID Lifetime expired, regenerate
-      session_regenerate_id(TRUE);
-      // Clean cache from _SESSION first, this cache used in ajax calls
-      if (isset($_SESSION['cache'])) { unset($_SESSION['cache']); }
-      $_SESSION['starttime'] = $currenttime;
-    }
-  } else {
-    $_SESSION['starttime']   = $currenttime;
-  }
+      // logfile('debug_auth.log', __LINE__ . ' session destroyed ' . json_encode($_SESSION));
 
-  session_commit();
+      if ($_SESSION['destroyed'] < time()-300)
+      {
+        // logfile('debug_auth.log', __LINE__ . ' < 300 - logout');
+
+        // Should not happen usually. This could be attack or due to unstable network.
+        // Remove all authentication status of this users session.
+        session_logout(true, 'Session destroyed');
+
+        return;
+      }
+
+      if (isset($_SESSION['new_session_id']))
+      {
+        // logfile('debug_auth.log', __LINE__ . ' got new_session_id: ' . $_SESSION['new_session_id']);
+
+        // Not fully expired yet. Could be lost cookie by unstable network.
+        // Try again to set proper session ID cookie.
+        // NOTE: Do not try to set session ID again if you would like to remove
+        // authentication flag.
+        session_commit();
+
+        session_id($_SESSION['new_session_id']);
+        // New session ID should exist
+        session_start();
+
+        return;
+      }
+   }
+
+   $currenttime   = time();
+   if ($lifetime_id != 1800 && is_numeric($lifetime_id) && $lifetime_id >= 300)
+   {
+     $lifetime_id = intval($lifetime_id);
+   } else {
+     $lifetime_id = 1800;
+   }
+
+   if (isset($_SESSION['starttime']))
+   {
+     // logfile('debug_auth.log', __LINE__ . ' ' . json_encode([$_SESSION['starttime'], $currenttime, $currenttime - $_SESSION['starttime']]));
+
+     if ($currenttime - $_SESSION['starttime'] >= $lifetime_id)
+     {
+       // logfile('debug_auth.log', __LINE__ . ' ' . 'session_regenerate() called at '.$currenttime);
+       // ID Lifetime expired, regenerate
+       my_session_regenerate_id();
+
+       // Clean cache from _SESSION first, this cache used in ajax calls
+       if (isset($_SESSION['cache'])) { unset($_SESSION['cache']); }
+       $_SESSION['starttime'] = $currenttime;
+     }
+   } else {
+     $_SESSION['starttime']   = $currenttime;
+   }
 }
 
 /**
+ * Safe session_regenerate_id that doesn't loose session
+ *
+ * Code borrowed from https://www.php.net/manual/en/function.session-regenerate-id.php
+ */
+function my_session_regenerate_id() {
+    // New session ID is required to set proper session ID
+    // when session ID is not set due to unstable network.
+    $new_session_id = session_create_id();
+    // logfile('debug_auth.log', __LINE__ . ' new_session_id: ' . $new_session_id);
+    $_SESSION['new_session_id'] = $new_session_id;
+
+    // Set destroy timestamp
+    $_SESSION['destroyed'] = time();
+
+    // Write and close current session;
+    session_commit();
+
+    $tmp = $_SESSION;  // Somehow the SESSION data is not kept
+
+    // Start session with new session ID
+    session_id($new_session_id);
+    ini_set('session.use_strict_mode', 0);
+    session_start();
+    ini_set('session.use_strict_mode', 1);
+
+    $_SESSION = $tmp;
+
+    // New session does not need them
+    unset($_SESSION['destroyed']);
+    unset($_SESSION['new_session_id']);
+}
+
+/**
  * Use this function for write to $_SESSION global var.
  * And prevent session blocking.
  * If value is NULL, this session variable will unset
@@ -669,7 +719,7 @@
  */
 function session_set_var($var, $value)
 {
-  //logfile('debug_auth.log', 'session_set_var() called at '.time());
+  //logfile('debug_auth.log', __LINE__ . ' ' . "session_set_var($var, $value) called at " . time());
   if (isset($_SESSION[$var]) && $_SESSION[$var] === $value) { return; } // Just return if session var unchanged
 
   @session_start(); // Unblock session again
