calls to functions deprecated in OpenSSL 1.1 replaced with compatible ones
[public/netxms.git] / src / server / core / main.cpp
index 957eca7..9378d9b 100644 (file)
@@ -1,6 +1,6 @@
 /*
 ** NetXMS - Network Management System
-** Copyright (C) 2003-2016 Raden Solutions
+** Copyright (C) 2003-2017 Raden Solutions
 **
 ** This program is free software; you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
@@ -35,9 +35,6 @@
 #include <errno.h>
 #include <psapi.h>
 #include <conio.h>
-#define open   _open
-#define write  _write
-#define close  _close
 #else
 #include <signal.h>
 #include <sys/wait.h>
@@ -70,10 +67,16 @@ extern Queue g_dciCacheLoaderQueue;
 void InitClientListeners();
 void InitMobileDeviceListeners();
 void InitCertificates();
+bool LoadServerCertificate(RSA **serverKey);
 void InitUsers();
 void CleanupUsers();
 void LoadPerfDataStorageDrivers();
 void ImportLocalConfiguration();
+void RegisterPredictionEngines();
+void ExecuteStartupScripts();
+void CloseAgentTunnels();
+void StopDataCollection();
+void StopObjectMaintenanceThreads();
 
 void ExecuteScheduledScript(const ScheduledTaskParameters *param);
 void MaintenanceModeEnter(const ScheduledTaskParameters *params);
@@ -103,10 +106,8 @@ THREAD_RESULT THREAD_CALL NodePoller(void *);
 THREAD_RESULT THREAD_CALL PollManager(void *);
 THREAD_RESULT THREAD_CALL EventProcessor(void *);
 THREAD_RESULT THREAD_CALL WatchdogThread(void *);
-THREAD_RESULT THREAD_CALL ClientListener(void *);
-THREAD_RESULT THREAD_CALL ClientListenerIPv6(void *);
-THREAD_RESULT THREAD_CALL MobileDeviceListener(void *);
-THREAD_RESULT THREAD_CALL MobileDeviceListenerIPv6(void *);
+THREAD_RESULT THREAD_CALL ClientListenerThread(void *);
+THREAD_RESULT THREAD_CALL MobileDeviceListenerThread(void *);
 THREAD_RESULT THREAD_CALL ISCListener(void *);
 THREAD_RESULT THREAD_CALL LocalAdminListener(void *);
 THREAD_RESULT THREAD_CALL SNMPTrapReceiver(void *);
@@ -114,6 +115,7 @@ THREAD_RESULT THREAD_CALL BeaconPoller(void *);
 THREAD_RESULT THREAD_CALL JobManagerThread(void *);
 THREAD_RESULT THREAD_CALL UptimeCalculator(void *);
 THREAD_RESULT THREAD_CALL ReportingServerConnector(void *);
+THREAD_RESULT THREAD_CALL TunnelListenerThread(void *arg);
 
 /**
  * Global variables
@@ -156,6 +158,8 @@ int g_requiredPolls = 1;
 DB_DRIVER g_dbDriver = NULL;
 ThreadPool NXCORE_EXPORTABLE *g_mainThreadPool = NULL;
 INT16 g_defaultAgentCacheMode = AGENT_CACHE_OFF;
+InetAddressList g_peerNodeAddrList;
+Condition g_dbPasswordReady(true);
 
 /**
  * Static data
@@ -163,6 +167,7 @@ INT16 g_defaultAgentCacheMode = AGENT_CACHE_OFF;
 static CONDITION m_condShutdown = INVALID_CONDITION_HANDLE;
 static THREAD m_thPollManager = INVALID_THREAD_HANDLE;
 static THREAD m_thSyncer = INVALID_THREAD_HANDLE;
+static THREAD s_tunnelListenerThread = INVALID_THREAD_HANDLE;
 static int m_nShutdownReason = SHUTDOWN_DEFAULT;
 static StringSet s_components;
 
@@ -328,6 +333,8 @@ static void LoadGlobalConfig()
                g_flags |= AF_ACTIVE_NETWORK_DISCOVERY;
    if (ConfigReadInt(_T("UseSNMPTrapsForDiscovery"), 0))
       g_flags |= AF_SNMP_TRAP_DISCOVERY;
+   if (ConfigReadInt(_T("UseSyslogForDiscovery"), 0))
+      g_flags |= AF_SYSLOG_DISCOVERY;
        if (ConfigReadInt(_T("ResolveNodeNames"), 1))
                g_flags |= AF_RESOLVE_NODE_NAMES;
        if (ConfigReadInt(_T("SyncNodeNamesWithDNS"), 0))
@@ -372,70 +379,86 @@ static void LoadGlobalConfig()
 /**
  * Initialize cryptografic functions
  */
-static BOOL InitCryptografy()
+static bool InitCryptografy()
 {
 #ifdef _WITH_ENCRYPTION
-       BOOL bResult = FALSE;
+       bool success = false;
 
    if (!InitCryptoLib(ConfigReadULong(_T("AllowedCiphers"), 0x7F)))
                return FALSE;
    nxlog_debug(4, _T("Supported ciphers: %s"), (const TCHAR *)NXCPGetSupportedCiphersAsText());
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+   OPENSSL_init_ssl(0, NULL);
+#else
    SSL_library_init();
    SSL_load_error_strings();
+#endif
 
-   TCHAR szKeyFile[MAX_PATH];
-       _tcscpy(szKeyFile, g_netxmsdDataDir);
-       _tcscat(szKeyFile, DFILE_KEYS);
-       g_pServerKey = LoadRSAKeys(szKeyFile);
-       if (g_pServerKey == NULL)
-       {
-          nxlog_debug(1, _T("Generating RSA key pair..."));
-               g_pServerKey = RSA_generate_key(NETXMS_RSA_KEYLEN, 17, NULL, 0);
-               if (g_pServerKey != NULL)
-               {
-                       int fd = _topen(szKeyFile, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0600);
-                       if (fd != -1)
-                       {
-                               UINT32 dwLen = i2d_RSAPublicKey(g_pServerKey, NULL);
-                               dwLen += i2d_RSAPrivateKey(g_pServerKey, NULL);
-                               BYTE *pKeyBuffer = (BYTE *)malloc(dwLen);
-
-                               BYTE *pBufPos = pKeyBuffer;
-                               i2d_RSAPublicKey(g_pServerKey, &pBufPos);
-                               i2d_RSAPrivateKey(g_pServerKey, &pBufPos);
-                               write(fd, &dwLen, sizeof(UINT32));
-                               write(fd, pKeyBuffer, dwLen);
-
-                          BYTE hash[SHA1_DIGEST_SIZE];
-                               CalculateSHA1Hash(pKeyBuffer, dwLen, hash);
-                               write(fd, hash, SHA1_DIGEST_SIZE);
-
-                               close(fd);
-                               free(pKeyBuffer);
-                               bResult = TRUE;
-                       }
+   if (LoadServerCertificate(&g_pServerKey))
+   {
+      nxlog_debug(1, _T("Server certificate loaded"));
+   }
+   if (g_pServerKey != NULL)
+   {
+      nxlog_debug(1, _T("Using server certificate key"));
+      success = true;
+   }
+   else
+   {
+      TCHAR szKeyFile[MAX_PATH];
+      _tcscpy(szKeyFile, g_netxmsdDataDir);
+      _tcscat(szKeyFile, DFILE_KEYS);
+      g_pServerKey = LoadRSAKeys(szKeyFile);
+      if (g_pServerKey == NULL)
+      {
+         nxlog_debug(1, _T("Generating RSA key pair..."));
+         g_pServerKey = RSAGenerateKey(NETXMS_RSA_KEYLEN);
+         if (g_pServerKey != NULL)
+         {
+            int fd = _topen(szKeyFile, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0600);
+            if (fd != -1)
+            {
+               UINT32 dwLen = i2d_RSAPublicKey(g_pServerKey, NULL);
+               dwLen += i2d_RSAPrivateKey(g_pServerKey, NULL);
+               BYTE *pKeyBuffer = (BYTE *)malloc(dwLen);
+
+               BYTE *pBufPos = pKeyBuffer;
+               i2d_RSAPublicKey(g_pServerKey, &pBufPos);
+               i2d_RSAPrivateKey(g_pServerKey, &pBufPos);
+               _write(fd, &dwLen, sizeof(UINT32));
+               _write(fd, pKeyBuffer, dwLen);
+
+               BYTE hash[SHA1_DIGEST_SIZE];
+               CalculateSHA1Hash(pKeyBuffer, dwLen, hash);
+               _write(fd, hash, SHA1_DIGEST_SIZE);
+
+               _close(fd);
+               free(pKeyBuffer);
+               success = true;
+            }
+            else
+            {
+               nxlog_debug(0, _T("Failed to open %s for writing"), szKeyFile);
+            }
+         }
          else
          {
-            nxlog_debug(0, _T("Failed to open %s for writing"), szKeyFile);
+            nxlog_debug(0, _T("Failed to generate RSA key"));
          }
-               }
+      }
       else
       {
-         nxlog_debug(0, _T("Failed to generate RSA key"));
+         success = true;
       }
-       }
-       else
-       {
-               bResult = TRUE;
-       }
+   }
 
        int iPolicy = ConfigReadInt(_T("DefaultEncryptionPolicy"), 1);
        if ((iPolicy < 0) || (iPolicy > 3))
                iPolicy = 1;
        SetAgentDEP(iPolicy);
 
-       return bResult;
+       return success;
 #else
        return TRUE;
 #endif
@@ -444,35 +467,80 @@ static BOOL InitCryptografy()
 /**
  * Check if process with given PID exists and is a NetXMS server process
  */
-static BOOL IsNetxmsdProcess(UINT32 dwPID)
+static bool IsNetxmsdProcess(UINT32 pid)
 {
 #ifdef _WIN32
-       HANDLE hProcess;
-       TCHAR szExtModule[MAX_PATH], szIntModule[MAX_PATH];
-       BOOL bRet = FALSE;
-
-       hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPID);
+       bool result = false;
+       HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (hProcess != NULL)
        {
+          TCHAR szExtModule[MAX_PATH], szIntModule[MAX_PATH];
                if ((GetModuleBaseName(hProcess, NULL, szExtModule, MAX_PATH) > 0) &&
-                               (GetModuleBaseName(GetCurrentProcess(), NULL, szIntModule, MAX_PATH) > 0))
+                        (GetModuleBaseName(GetCurrentProcess(), NULL, szIntModule, MAX_PATH) > 0))
                {
-                       bRet = !_tcsicmp(szExtModule, szIntModule);
+                       result = (_tcsicmp(szExtModule, szIntModule) == 0);
                }
                else
                {
                        // Cannot read process name, for safety assume that it's a server process
-                       bRet = TRUE;
+                       result = true;
                }
                CloseHandle(hProcess);
        }
-       return bRet;
+       return result;
 #else
-       return (kill((pid_t)dwPID, 0) != -1);
+       return kill((pid_t)pid, 0) != -1;
 #endif
 }
 
 /**
+ * Check if remote netxmsd is running
+ */
+static bool PeerNodeIsRunning(const InetAddress& addr)
+{
+   bool result = false;
+
+   TCHAR keyFile[MAX_PATH];
+   _tcscpy(keyFile, g_netxmsdDataDir);
+   _tcscat(keyFile, DFILE_KEYS);
+   RSA *key = LoadRSAKeys(keyFile);
+
+   AgentConnection *ac = new AgentConnection(addr);
+   if (ac->connect(key))
+   {
+      TCHAR result[MAX_RESULT_LENGTH];
+#ifdef _WIN32
+      UINT32 rcc = ac->getParameter(_T("Process.Count(netxmsd.exe)"), MAX_RESULT_LENGTH, result);
+#else
+      UINT32 rcc = ac->getParameter(_T("Process.Count(netxmsd)"), MAX_RESULT_LENGTH, result);
+#endif
+      ac->decRefCount();
+      if (key != NULL)
+         RSA_free(key);
+      if (rcc == ERR_SUCCESS)
+      {
+         return _tcstol(result, NULL, 10) > 0;
+      }
+   }
+   else
+   {
+      ac->decRefCount();
+      if (key != NULL)
+         RSA_free(key);
+   }
+
+   UINT16 port = (UINT16)ConfigReadInt(_T("ClientListenerPort"), SERVER_LISTEN_PORT_FOR_CLIENTS);
+   SOCKET s = ConnectToHost(addr, port, 5000);
+   if (s != INVALID_SOCKET)
+   {
+      shutdown(s, SHUT_RDWR);
+      closesocket(s);
+      result = true;
+   }
+   return result;
+}
+
+/**
  * Database event handler
  */
 static void DBEventHandler(DWORD dwEvent, const WCHAR *pszArg1, const WCHAR *pszArg2, bool connLost, void *userArg)
@@ -542,6 +610,19 @@ static void OracleSessionInitCallback(DB_HANDLE hdb)
 }
 
 /**
+ * Get database password
+ */
+static void GetDatabasePassword()
+{
+   if (_tcscmp(g_szDbPassword, _T("?")))
+      return;
+
+   nxlog_write(MSG_WAITING_FOR_DB_PASSWORD, NXLOG_INFO, NULL);
+   g_dbPasswordReady.wait(INFINITE);
+   nxlog_debug(1, _T("Database password received"));
+}
+
+/**
  * Server initialization
  */
 BOOL NXCORE_EXPORTABLE Initialize()
@@ -563,9 +644,9 @@ BOOL NXCORE_EXPORTABLE Initialize()
                    ((g_flags & AF_DAEMON) ? 0 : NXLOG_PRINT_TO_STDOUT),
                    _T("LIBNXSRV.DLL"),
 #ifdef _WIN32
-                                      0, NULL, MSG_DEBUG))
+                                      0, NULL, MSG_DEBUG, MSG_DEBUG_TAG, MSG_OTHER))
 #else
-                                      g_dwNumMessages, g_szMessages, MSG_DEBUG))
+                                      g_dwNumMessages, g_szMessages, MSG_DEBUG, MSG_DEBUG_TAG, MSG_OTHER))
 #endif
    {
                _ftprintf(stderr, _T("FATAL ERROR: Cannot open log file\n"));
@@ -627,6 +708,13 @@ BOOL NXCORE_EXPORTABLE Initialize()
        if (g_dbDriver == NULL)
                return FALSE;
 
+   // Start local administrative interface listener if required
+   if (g_flags & AF_ENABLE_LOCAL_CONSOLE)
+      ThreadCreate(LocalAdminListener, 0, NULL);
+
+       // Wait for database password if needed
+       GetDatabasePassword();
+
        // Connect to database
        DB_HANDLE hdbBootstrap = NULL;
        TCHAR errorText[DBDRV_MAX_ERROR_TEXT];
@@ -645,10 +733,17 @@ BOOL NXCORE_EXPORTABLE Initialize()
        nxlog_debug(1, _T("Successfully connected to database %s@%s"), g_szDbName, g_szDbServer);
 
        // Check database schema version
-       int schemaVersion = DBGetSchemaVersion(hdbBootstrap);
-       if (schemaVersion != DB_FORMAT_VERSION)
+       INT32 schemaVersionMajor, schemaVersionMinor;
+       if (!DBGetSchemaVersion(hdbBootstrap, &schemaVersionMajor, &schemaVersionMinor))
        {
-               nxlog_write(MSG_WRONG_DB_VERSION, EVENTLOG_ERROR_TYPE, "dd", schemaVersion, DB_FORMAT_VERSION);
+          nxlog_write(MSG_UNABLE_TO_GET_DB_SCHEMA_VERSION, NXLOG_ERROR, NULL);
+      DBDisconnect(hdbBootstrap);
+      return FALSE;
+       }
+
+       if ((schemaVersionMajor != DB_SCHEMA_VERSION_MAJOR) || (schemaVersionMinor != DB_SCHEMA_VERSION_MINOR))
+       {
+               nxlog_write(MSG_WRONG_DB_SCHEMA_VERSION, NXLOG_ERROR, "dddd", schemaVersionMajor, schemaVersionMinor, DB_SCHEMA_VERSION_MAJOR, DB_SCHEMA_VERSION_MINOR);
                DBDisconnect(hdbBootstrap);
                return FALSE;
        }
@@ -675,7 +770,10 @@ BOOL NXCORE_EXPORTABLE Initialize()
 
    UINT32 lrt = ConfigReadULong(_T("LongRunningQueryThreshold"), 0);
    if (lrt != 0)
+   {
       DBSetLongRunningThreshold(lrt);
+      nxlog_write_generic(NXLOG_INFO, _T("Long running query threshold set at %d milliseconds"), lrt);
+   }
 
    MetaDataPreLoad();
 
@@ -694,34 +792,41 @@ BOOL NXCORE_EXPORTABLE Initialize()
       _sntprintf(buffer, 256, UINT64X_FMT(_T("016")), g_serverId);
                MetaDataWriteStr(_T("ServerID"), buffer);
        }
-       nxlog_debug(1, _T("Server ID ") UINT64X_FMT(_T("016")), g_serverId);
+       nxlog_write_generic(NXLOG_INFO, _T("Server ID ") UINT64X_FMT(_T("016")), g_serverId);
 
        // Initialize locks
 retry_db_lock:
    InetAddress addr;
        if (!InitLocks(&addr, buffer))
        {
-      if (!addr.isValid())    // Some SQL problems
-               {
-                       nxlog_write(MSG_INIT_LOCKS_FAILED, EVENTLOG_ERROR_TYPE, NULL);
-               }
-               else     // Database already locked by another server instance
+               if (addr.isValidUnicast())     // Database already locked by another server instance
                {
                        // Check for lock from crashed/terminated local process
                        if (GetLocalIpAddr().equals(addr))
                        {
-                               UINT32 dwPID;
-
-                               dwPID = ConfigReadULong(_T("DBLockPID"), 0);
-                               if (!IsNetxmsdProcess(dwPID) || (dwPID == GetCurrentProcessId()))
+                               UINT32 pid = ConfigReadULong(_T("DBLockPID"), 0);
+                               if (!IsNetxmsdProcess(pid) || (pid == GetCurrentProcessId()))
                                {
                                        UnlockDB();
                                        nxlog_write(MSG_DB_LOCK_REMOVED, EVENTLOG_INFORMATION_TYPE, NULL);
                                        goto retry_db_lock;
                                }
                        }
-                       nxlog_write(MSG_DB_LOCKED, EVENTLOG_ERROR_TYPE, "As", &addr, buffer);
+                       else if (g_peerNodeAddrList.hasAddress(addr))
+                       {
+                          if (!PeerNodeIsRunning(addr))
+                          {
+               UnlockDB();
+               nxlog_write(MSG_DB_LOCK_REMOVED, EVENTLOG_INFORMATION_TYPE, NULL);
+               goto retry_db_lock;
+                          }
+                       }
+                       nxlog_write(MSG_DB_LOCKED, EVENTLOG_ERROR_TYPE, "Is", &addr, buffer);
                }
+               else
+      {
+         nxlog_write(MSG_INIT_LOCKS_FAILED, EVENTLOG_ERROR_TYPE, NULL);
+      }
                return FALSE;
        }
        g_flags |= AF_DB_LOCKED;
@@ -769,12 +874,16 @@ retry_db_lock:
        // Load and compile scripts
        LoadScripts();
 
+       // Initialize persistent storage
+       PersistentStorageInit();
+
        // Initialize watchdog
        WatchdogInit();
 
        // Load modules
        if (!LoadNetXMSModules())
                return FALSE;   // Mandatory module not loaded
+       RegisterPredictionEngines();
 
        // Initialize mailer and SMS sender
        InitMailer();
@@ -808,11 +917,6 @@ retry_db_lock:
                return FALSE;
        nxlog_debug(1, _T("Objects loaded and initialized"));
 
-       // Initialize situations
-       if (!SituationsInit())
-               return FALSE;
-       nxlog_debug(1, _T("Situations loaded and initialized"));
-
        // Initialize and load event actions
        if (!InitActions())
        {
@@ -826,8 +930,7 @@ retry_db_lock:
 
        // Initialize data collection subsystem
    LoadPerfDataStorageDrivers();
-       if (!InitDataCollector())
-               return FALSE;
+       InitDataCollector();
 
        InitLogAccess();
        FileUploadJob::init();
@@ -868,10 +971,6 @@ retry_db_lock:
        // Start database _T("lazy") write thread
        StartDBWriter();
 
-       // Start local administartive interface listener if required
-       if (ConfigReadInt(_T("EnableAdminInterface"), 1))
-               ThreadCreate(LocalAdminListener, 0, NULL);
-
        // Start beacon host poller
        ThreadCreate(BeaconPoller, 0, NULL);
 
@@ -892,20 +991,24 @@ retry_db_lock:
    RegisterSchedulerTaskHandler(_T("Maintenance.Leave"), MaintenanceModeLeave, SYSTEM_ACCESS_SCHEDULE_MAINTENANCE);
        RegisterSchedulerTaskHandler(_T("Policy.Deploy"), ScheduleDeployPolicy, 0); //No access right beacause it will be used only by server
        RegisterSchedulerTaskHandler(_T("Policy.Uninstall"), ScheduleUninstallPolicy, 0); //No access right beacause it will be used only by server
+   RegisterSchedulerTaskHandler(ALARM_SUMMARY_EMAIL_TASK_ID, SendAlarmSummaryEmail, 0); //No access right beacause it will be used only by server
    InitializeTaskScheduler();
 
+   // Send summary emails
+   if (ConfigReadInt(_T("EnableAlarmSummaryEmails"), 0))
+      EnableAlarmSummaryEmails();
+   else
+      RemoveScheduledTaskByHandlerId(ALARM_SUMMARY_EMAIL_TASK_ID);
+
        // Allow clients to connect
-       ThreadCreate(ClientListener, 0, NULL);
-#ifdef WITH_IPV6
-       ThreadCreate(ClientListenerIPv6, 0, NULL);
-#endif
+       ThreadCreate(ClientListenerThread, 0, NULL);
 
        // Allow mobile devices to connect
        InitMobileDeviceListeners();
-       ThreadCreate(MobileDeviceListener, 0, NULL);
-#ifdef WITH_IPV6
-       ThreadCreate(MobileDeviceListenerIPv6, 0, NULL);
-#endif
+       ThreadCreate(MobileDeviceListenerThread, 0, NULL);
+
+       // Agent tunnels
+   s_tunnelListenerThread = ThreadCreateEx(TunnelListenerThread, 0, NULL);
 
        // Start uptime calculator for SLM
        ThreadCreate(UptimeCalculator, 0, NULL);
@@ -926,6 +1029,8 @@ retry_db_lock:
    StartZMQConnector();
 #endif
 
+   ExecuteStartupScripts();
+
        g_flags |= AF_SERVER_INITIALIZED;
        nxlog_debug(1, _T("Server initialization completed"));
        return TRUE;
@@ -971,6 +1076,9 @@ void NXCORE_EXPORTABLE Shutdown()
        // Wait for critical threads
        ThreadJoin(m_thPollManager);
        ThreadJoin(m_thSyncer);
+       ThreadJoin(s_tunnelListenerThread);
+
+       CloseAgentTunnels();
 
        // Call shutdown functions for the modules
    // CALL_ALL_MODULES cannot be used here because it checks for shutdown flag
@@ -980,17 +1088,23 @@ void NXCORE_EXPORTABLE Shutdown()
                        g_pModuleList[i].pfShutdown();
        }
 
+   StopDataCollection();
+   StopObjectMaintenanceThreads();
+
    DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
-       SaveObjects(hdb);
+       SaveObjects(hdb, INVALID_INDEX, true);
        nxlog_debug(2, _T("All objects saved to database"));
-       SaveUsers(hdb);
+       SaveUsers(hdb, INVALID_INDEX);
        nxlog_debug(2, _T("All users saved to database"));
+   UpdatePStorageDatabase(hdb, INVALID_INDEX);
+       nxlog_debug(2, _T("All persistent storage values saved"));
        DBConnectionPoolReleaseConnection(hdb);
 
        StopDBWriter();
        nxlog_debug(1, _T("Database writer stopped"));
 
        CleanupUsers();
+       PersistentStorageDestroy();
 
        // Remove database lock
        UnlockDB();
@@ -1007,8 +1121,7 @@ void NXCORE_EXPORTABLE Shutdown()
        ThreadPoolDestroy(g_agentConnectionThreadPool);
    ThreadPoolDestroy(g_mainThreadPool);
    MsgWaitQueue::shutdown();
-
-       delete g_pScriptLibrary;
+   WatchdogShutdown();
 
        nxlog_debug(1, _T("Server shutdown complete"));
        nxlog_close();
@@ -1038,10 +1151,12 @@ void NXCORE_EXPORTABLE FastShutdown()
        ConditionSet(m_condShutdown);
 
        DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
-       SaveObjects(hdb);
+       SaveObjects(hdb, INVALID_INDEX, true);
        DbgPrintf(2, _T("All objects saved to database"));
-       SaveUsers(hdb);
+       SaveUsers(hdb, INVALID_INDEX);
        DbgPrintf(2, _T("All users saved to database"));
+   UpdatePStorageDatabase(hdb, INVALID_INDEX);
+       DbgPrintf(2, _T("All persistent storage values saved"));
        DBConnectionPoolReleaseConnection(hdb);
 
        // Remove database lock first, because we have a chance to lose DB connection