diff --git a/mRemoteV1/App/Export.cs b/mRemoteV1/App/Export.cs index 06175fbfd..068ea6856 100644 --- a/mRemoteV1/App/Export.cs +++ b/mRemoteV1/App/Export.cs @@ -67,14 +67,14 @@ namespace mRemoteNG.App } } - private static void SaveExportFile(string fileName, ConnectionsSaver.Format saveFormat, SaveFilter saveFilter, ConnectionInfo exportTarget) + private static void SaveExportFile(string fileName, SaveFormat saveFormat, SaveFilter saveFilter, ConnectionInfo exportTarget) { try { ISerializer serializer; switch (saveFormat) { - case ConnectionsSaver.Format.mRXML: + case SaveFormat.mRXML: var cryptographyProvider = new CryptoProviderFactoryFromSettings().Build(); var rootNode = exportTarget.GetRootParent() as RootNodeInfo; var connectionNodeSerializer = new XmlConnectionNodeSerializer26( @@ -83,7 +83,7 @@ namespace mRemoteNG.App saveFilter); serializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer); break; - case ConnectionsSaver.Format.mRCSV: + case SaveFormat.mRCSV: serializer = new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialProviderCatalog); break; default: diff --git a/mRemoteV1/App/Runtime.cs b/mRemoteV1/App/Runtime.cs index 70878db22..dd5075133 100644 --- a/mRemoteV1/App/Runtime.cs +++ b/mRemoteV1/App/Runtime.cs @@ -4,7 +4,6 @@ using System.Security; using System.Threading; using System.Windows.Forms; using mRemoteNG.App.Info; -using mRemoteNG.Config.Connections; using mRemoteNG.Config.Connections.Multiuser; using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.Putty; @@ -13,7 +12,6 @@ using mRemoteNG.Credential; using mRemoteNG.Credential.Repositories; using mRemoteNG.Messages; using mRemoteNG.Security; -using mRemoteNG.Security.SymmetricEncryption; using mRemoteNG.Tools; using mRemoteNG.Tree.Root; using mRemoteNG.UI; @@ -40,8 +38,6 @@ namespace mRemoteNG.App public static MessageCollector MessageCollector { get; } = new MessageCollector(); public static NotificationAreaIcon NotificationAreaIcon { get; set; } public static RemoteConnectionsSyncronizer RemoteConnectionsSyncronizer { get; set; } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public static DateTime LastSqlUpdate { get; set; } public static ExternalToolsService ExternalToolsService { get; } = new ExternalToolsService(); public static SecureString EncryptionKey { get; set; } = new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString(); public static ICredentialRepositoryList CredentialProviderCatalog { get; } = new CredentialRepositoryList(); @@ -97,7 +93,7 @@ namespace mRemoteNG.App if (Settings.Default.UseSQLServer) { - LastSqlUpdate = DateTime.Now; + ConnectionsService.LastSqlUpdate = DateTime.Now; } else { @@ -210,98 +206,9 @@ namespace mRemoteNG.App private static void SaveConnectionsBGd() { Monitor.Enter(SaveLock); - SaveConnections(); + ConnectionsService.SaveConnections(); Monitor.Exit(SaveLock); } - - public static void SaveConnections() - { - if (ConnectionsService.ConnectionTreeModel == null) return; - if (!ConnectionsService.IsConnectionsFileLoaded) return; - - try - { - RemoteConnectionsSyncronizer?.Disable(); - - var connectionsSaver = new ConnectionsSaver(); - - if (!Settings.Default.UseSQLServer) - connectionsSaver.ConnectionFileName = ConnectionsService.GetStartupConnectionFileName(); - - connectionsSaver.SaveFilter = new SaveFilter(); - connectionsSaver.ConnectionTreeModel = ConnectionsService.ConnectionTreeModel; - - if (Settings.Default.UseSQLServer) - { - connectionsSaver.SaveFormat = ConnectionsSaver.Format.SQL; - connectionsSaver.SQLHost = Settings.Default.SQLHost; - connectionsSaver.SQLDatabaseName = Settings.Default.SQLDatabaseName; - connectionsSaver.SQLUsername = Settings.Default.SQLUser; - var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); - connectionsSaver.SQLPassword = cryptographyProvider.Decrypt(Settings.Default.SQLPass, EncryptionKey); - } - - connectionsSaver.SaveConnections(); - - if (Settings.Default.UseSQLServer) - LastSqlUpdate = DateTime.Now; - } - catch (Exception ex) - { - MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strConnectionsFileCouldNotBeSaved + Environment.NewLine + ex.Message); - } - finally - { - RemoteConnectionsSyncronizer?.Enable(); - } - } - - public static void SaveConnectionsAs() - { - var connectionsSave = new ConnectionsSaver(); - - try - { - RemoteConnectionsSyncronizer?.Disable(); - - using (var saveFileDialog = new SaveFileDialog()) - { - saveFileDialog.CheckPathExists = true; - saveFileDialog.InitialDirectory = ConnectionsFileInfo.DefaultConnectionsPath; - saveFileDialog.FileName = ConnectionsFileInfo.DefaultConnectionsFile; - saveFileDialog.OverwritePrompt = true; - saveFileDialog.Filter = $@"{Language.strFiltermRemoteXML}|*.xml|{Language.strFilterAll}|*.*"; - - if (saveFileDialog.ShowDialog(FrmMain.Default) != DialogResult.OK) return; - - connectionsSave.SaveFormat = ConnectionsSaver.Format.mRXML; - connectionsSave.ConnectionFileName = saveFileDialog.FileName; - connectionsSave.SaveFilter = new SaveFilter(); - connectionsSave.ConnectionTreeModel = ConnectionsService.ConnectionTreeModel; - - connectionsSave.SaveConnections(); - - if (saveFileDialog.FileName == ConnectionsService.GetDefaultStartupConnectionFileName()) - { - Settings.Default.LoadConsFromCustomLocation = false; - } - else - { - Settings.Default.LoadConsFromCustomLocation = true; - Settings.Default.CustomConsPath = saveFileDialog.FileName; - } - } - - } - catch (Exception ex) - { - MessageCollector.AddExceptionMessage(string.Format(Language.strConnectionsFileCouldNotSaveAs, connectionsSave.ConnectionFileName), ex); - } - finally - { - RemoteConnectionsSyncronizer?.Enable(); - } - } #endregion } } \ No newline at end of file diff --git a/mRemoteV1/App/Shutdown.cs b/mRemoteV1/App/Shutdown.cs index dad75d855..3d81dcbd5 100644 --- a/mRemoteV1/App/Shutdown.cs +++ b/mRemoteV1/App/Shutdown.cs @@ -55,7 +55,7 @@ namespace mRemoteNG.App private static void SaveConnections() { if (Settings.Default.SaveConsOnExit) - Runtime.SaveConnections(); + Runtime.ConnectionsService.SaveConnections(); } private static void SaveSettings(Control quickConnectToolStrip, ExternalToolsToolStrip externalToolsToolStrip, FrmMain frmMain) diff --git a/mRemoteV1/App/Startup.cs b/mRemoteV1/App/Startup.cs index dd650c312..d4dfdfa8e 100644 --- a/mRemoteV1/App/Startup.cs +++ b/mRemoteV1/App/Startup.cs @@ -57,8 +57,6 @@ namespace mRemoteNG.App public void CreateConnectionsProvider(MessageCollector messageCollector) { messageCollector.AddMessage(MessageClass.DebugMsg, "Determining if we need a database syncronizer"); - _frmMain.AreWeUsingSqlServerForSavingConnections = Settings.Default.UseSQLServer; - if (!Settings.Default.UseSQLServer) return; messageCollector.AddMessage(MessageClass.DebugMsg, "Creating database syncronizer"); Runtime.RemoteConnectionsSyncronizer = new RemoteConnectionsSyncronizer(new SqlConnectionsUpdateChecker()); diff --git a/mRemoteV1/Config/Connections/ConnectionsLoader.cs b/mRemoteV1/Config/Connections/ConnectionsLoader.cs deleted file mode 100644 index def8af099..000000000 --- a/mRemoteV1/Config/Connections/ConnectionsLoader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using mRemoteNG.Config.Putty; -using mRemoteNG.Tree; - -namespace mRemoteNG.Config.Connections -{ - public class ConnectionsLoader - { - /// - /// Load connections from a source. is ignored if - /// is true. - /// - /// - /// - /// - public ConnectionTreeModel LoadConnections(bool useDatabase, bool import, string connectionFileName) - { - ConnectionTreeModel connectionTreeModel; - - if (useDatabase) - { - var sqlLoader = new SqlConnectionsLoader(); - connectionTreeModel = sqlLoader.Load(); - } - else - { - var xmlLoader = new XmlConnectionsLoader(connectionFileName); - connectionTreeModel = xmlLoader.Load(); - } - - if (connectionTreeModel == null) - connectionTreeModel = new ConnectionTreeModel(); - - if (!import) - AddPuttySessions(connectionTreeModel); - - return connectionTreeModel; - } - - private void AddPuttySessions(ConnectionTreeModel connectionTreeModel) - { - PuttySessionsManager.Instance.AddSessions(); - connectionTreeModel.RootNodes.AddRange(PuttySessionsManager.Instance.RootPuttySessionsNodes); - } - } -} \ No newline at end of file diff --git a/mRemoteV1/Config/Connections/ConnectionsSavedEventArgs.cs b/mRemoteV1/Config/Connections/ConnectionsSavedEventArgs.cs new file mode 100644 index 000000000..a1386b79a --- /dev/null +++ b/mRemoteV1/Config/Connections/ConnectionsSavedEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using mRemoteNG.Tree; + +namespace mRemoteNG.Config.Connections +{ + public class ConnectionsSavedEventArgs + { + public ConnectionTreeModel ModelThatWasSaved { get; } + public bool PreviouslyUsingDatabase { get; } + public bool UsingDatabase { get; } + public string ConnectionFileName { get; } + + public ConnectionsSavedEventArgs(ConnectionTreeModel modelThatWasSaved, bool previouslyUsingDatabase, bool usingDatabase, string connectionFileName) + { + if (modelThatWasSaved == null) + throw new ArgumentNullException(nameof(modelThatWasSaved)); + + ModelThatWasSaved = modelThatWasSaved; + PreviouslyUsingDatabase = previouslyUsingDatabase; + UsingDatabase = usingDatabase; + ConnectionFileName = connectionFileName; + } + } +} diff --git a/mRemoteV1/Config/Connections/ConnectionsSaver.cs b/mRemoteV1/Config/Connections/ConnectionsSaver.cs deleted file mode 100644 index c53bd512f..000000000 --- a/mRemoteV1/Config/Connections/ConnectionsSaver.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Data.SqlClient; -using System.Globalization; -using System.Linq; -using System.Security; -using System.Windows.Forms; -using System.Xml; -using mRemoteNG.App; -using mRemoteNG.App.Info; -using mRemoteNG.Config.DatabaseConnectors; -using mRemoteNG.Config.DataProviders; -using mRemoteNG.Config.Serializers; -using mRemoteNG.Config.Serializers.Versioning; -using mRemoteNG.Container; -using mRemoteNG.Messages; -using mRemoteNG.Security; -using mRemoteNG.Security.Factories; -using mRemoteNG.Security.SymmetricEncryption; -using mRemoteNG.Tools; -using mRemoteNG.Tree; -using mRemoteNG.Tree.Root; -using mRemoteNG.UI.Forms; -// ReSharper disable UnusedAutoPropertyAccessor.Global - -namespace mRemoteNG.Config.Connections -{ - public class ConnectionsSaver - { - public enum Format - { - None, - mRXML, - mRCSV, - SQL - } - - private SecureString _password = Runtime.EncryptionKey; - - #region Public Properties - public string SQLHost {get; set;} - public string SQLDatabaseName {get; set;} - public string SQLUsername {get; set;} - public string SQLPassword {get; set;} - - public string ConnectionFileName {get; set;} - public TreeNode RootTreeNode {get; set;} - public Format SaveFormat {get; set;} - public SaveFilter SaveFilter {get; set;} - public ConnectionTreeModel ConnectionTreeModel { get; set; } - #endregion - - #region Public Methods - public void SaveConnections() - { - switch (SaveFormat) - { - case Format.SQL: - SaveToSql(); - break; - case Format.mRCSV: - SaveToMremotengFormattedCsv(); - break; - default: - SaveToXml(); - FrmMain.Default.ConnectionsFileName = ConnectionFileName; - break; - } - FrmMain.Default.AreWeUsingSqlServerForSavingConnections = SaveFormat == Format.SQL; - } - #endregion - - #region SQL - private void SaveToSql() - { - var sqlConnector = DatabaseConnectorFactory.SqlDatabaseConnectorFromSettings(); - sqlConnector.Connect(); - var databaseVersionVerifier = new SqlDatabaseVersionVerifier(sqlConnector); - - if (!databaseVersionVerifier.VerifyDatabaseVersion()) - { - Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strErrorConnectionListSaveFailed); - return; - } - - var rootTreeNode = Runtime.ConnectionsService.ConnectionTreeModel.RootNodes.OfType().First(); - - UpdateRootNodeTable(rootTreeNode, sqlConnector); - UpdateConnectionsTable(rootTreeNode, sqlConnector); - UpdateUpdatesTable(sqlConnector); - - sqlConnector.Disconnect(); - sqlConnector.Dispose(); - } - - private void UpdateRootNodeTable(RootNodeInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector) - { - var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); - string strProtected; - if (rootTreeNode != null) - { - if (rootTreeNode.Password) - { - _password = rootTreeNode.PasswordString.ConvertToSecureString(); - strProtected = cryptographyProvider.Encrypt("ThisIsProtected", _password); - } - else - { - strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", _password); - } - } - else - { - strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", _password); - } - - var sqlQuery = new SqlCommand("DELETE FROM tblRoot", sqlDatabaseConnector.SqlConnection); - sqlQuery.ExecuteNonQuery(); - - if (rootTreeNode != null) - { - sqlQuery = - new SqlCommand( - "INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES(\'" + - MiscTools.PrepareValueForDB(rootTreeNode.Name) + "\', 0, \'" + strProtected + "\'," + - ConnectionsFileInfo.ConnectionFileVersion.ToString(CultureInfo.InvariantCulture) + ")", - sqlDatabaseConnector.SqlConnection); - sqlQuery.ExecuteNonQuery(); - } - else - { - Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, $"UpdateRootNodeTable: rootTreeNode was null. Could not insert!"); - } - } - - private void UpdateConnectionsTable(ContainerInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector) - { - var sqlQuery = new SqlCommand("DELETE FROM tblCons", sqlDatabaseConnector.SqlConnection); - sqlQuery.ExecuteNonQuery(); - var serializer = new DataTableSerializer(SaveFilter); - var dataTable = serializer.Serialize(rootTreeNode); - var dataProvider = new SqlDataProvider(sqlDatabaseConnector); - dataProvider.Save(dataTable); - } - - private void UpdateUpdatesTable(SqlDatabaseConnector sqlDatabaseConnector) - { - var sqlQuery = new SqlCommand("DELETE FROM tblUpdate", sqlDatabaseConnector.SqlConnection); - sqlQuery.ExecuteNonQuery(); - sqlQuery = new SqlCommand("INSERT INTO tblUpdate (LastUpdate) VALUES(\'" + MiscTools.DBDate(DateTime.Now) + "\')", sqlDatabaseConnector.SqlConnection); - sqlQuery.ExecuteNonQuery(); - } - #endregion - - private void SaveToXml() - { - try - { - var cryptographyProvider = new CryptoProviderFactoryFromSettings().Build(); - var connectionNodeSerializer = new XmlConnectionNodeSerializer26( - cryptographyProvider, - ConnectionTreeModel.RootNodes.OfType().First().PasswordString.ConvertToSecureString(), - SaveFilter); - var xmlConnectionsSerializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer) - { - UseFullEncryption = mRemoteNG.Settings.Default.EncryptCompleteConnectionsFile - }; - var xml = xmlConnectionsSerializer.Serialize(ConnectionTreeModel); - - var fileDataProvider = new FileDataProviderWithRollingBackup(ConnectionFileName); - fileDataProvider.Save(xml); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("SaveToXml failed", ex); - } - } - - private void SaveToMremotengFormattedCsv() - { - var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(SaveFilter, Runtime.CredentialProviderCatalog); - var dataProvider = new FileDataProvider(ConnectionFileName); - var csvContent = csvConnectionsSerializer.Serialize(ConnectionTreeModel); - dataProvider.Save(csvContent); - } - } -} \ No newline at end of file diff --git a/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs b/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs new file mode 100644 index 000000000..feb45bfcb --- /dev/null +++ b/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs @@ -0,0 +1,34 @@ +using System; +using mRemoteNG.App; +using mRemoteNG.Config.DataProviders; +using mRemoteNG.Config.Serializers; +using mRemoteNG.Security; +using mRemoteNG.Tree; + +namespace mRemoteNG.Config.Connections +{ + public class CsvConnectionsSaver : ISaver + { + private readonly string _connectionFileName; + private readonly SaveFilter _saveFilter; + + public CsvConnectionsSaver(string connectionFileName, SaveFilter saveFilter) + { + if (string.IsNullOrEmpty(connectionFileName)) + throw new ArgumentException($"Argument '{nameof(connectionFileName)}' cannot be null or empty"); + if (saveFilter == null) + throw new ArgumentNullException(nameof(saveFilter)); + + _connectionFileName = connectionFileName; + _saveFilter = saveFilter; + } + + public void Save(ConnectionTreeModel connectionTreeModel) + { + var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialProviderCatalog); + var dataProvider = new FileDataProvider(_connectionFileName); + var csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel); + dataProvider.Save(csvContent); + } + } +} diff --git a/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs b/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs index d5e67cff6..93bdcfa0c 100644 --- a/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs +++ b/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs @@ -9,7 +9,6 @@ namespace mRemoteNG.Config.Connections.Multiuser { private readonly Timer _updateTimer; private readonly IConnectionsUpdateChecker _updateChecker; - private readonly ConnectionsSaver _connectionsSaver; public double TimerIntervalInMilliseconds { @@ -20,7 +19,6 @@ namespace mRemoteNG.Config.Connections.Multiuser { _updateChecker = updateChecker; _updateTimer = new Timer(3000); - _connectionsSaver = new ConnectionsSaver { SaveFormat = ConnectionsSaver.Format.SQL }; SetEventListeners(); } @@ -33,22 +31,12 @@ namespace mRemoteNG.Config.Connections.Multiuser ConnectionsUpdateAvailable += Load; } - public void Load() - { - Runtime.ConnectionsService.ConnectionTreeModel = Runtime.ConnectionsService.LoadConnections(mRemoteNG.Settings.Default.UseSQLServer, false, ""); - } - private void Load(object sender, ConnectionsUpdateAvailableEventArgs args) { - Load(); + Runtime.ConnectionsService.ConnectionTreeModel = Runtime.ConnectionsService.LoadConnections(true, false, ""); args.Handled = true; } - public void Save() - { - _connectionsSaver.SaveConnections(); - } - public void Enable() { _updateTimer.Start(); diff --git a/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs b/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs index 42d78c061..fdd8dc1bb 100644 --- a/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs +++ b/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs @@ -64,7 +64,7 @@ namespace mRemoteNG.Config.Connections private bool CheckIfIAmTheLastOneUpdated(DateTime lastUpdateInDb) { - DateTime LastSqlUpdateWithoutMilliseconds = new DateTime(Runtime.LastSqlUpdate.Ticks - (Runtime.LastSqlUpdate.Ticks % TimeSpan.TicksPerSecond), Runtime.LastSqlUpdate.Kind); + DateTime LastSqlUpdateWithoutMilliseconds = new DateTime(Runtime.ConnectionsService.LastSqlUpdate.Ticks - (Runtime.ConnectionsService.LastSqlUpdate.Ticks % TimeSpan.TicksPerSecond), Runtime.ConnectionsService.LastSqlUpdate.Kind); return lastUpdateInDb == LastSqlUpdateWithoutMilliseconds; } diff --git a/mRemoteV1/Config/Connections/SaveFormat.cs b/mRemoteV1/Config/Connections/SaveFormat.cs new file mode 100644 index 000000000..0ca1d9b05 --- /dev/null +++ b/mRemoteV1/Config/Connections/SaveFormat.cs @@ -0,0 +1,10 @@ +namespace mRemoteNG.Config.Connections +{ + public enum SaveFormat + { + None, + mRXML, + mRCSV, + SQL + } +} diff --git a/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs b/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs new file mode 100644 index 000000000..9ac227911 --- /dev/null +++ b/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs @@ -0,0 +1,113 @@ +using System; +using System.Data.SqlClient; +using System.Globalization; +using System.Linq; +using System.Security; +using mRemoteNG.App; +using mRemoteNG.App.Info; +using mRemoteNG.Config.DatabaseConnectors; +using mRemoteNG.Config.DataProviders; +using mRemoteNG.Config.Serializers; +using mRemoteNG.Config.Serializers.Versioning; +using mRemoteNG.Container; +using mRemoteNG.Messages; +using mRemoteNG.Security; +using mRemoteNG.Security.SymmetricEncryption; +using mRemoteNG.Tools; +using mRemoteNG.Tree; +using mRemoteNG.Tree.Root; + +namespace mRemoteNG.Config.Connections +{ + public class SqlConnectionsSaver : ISaver + { + private SecureString _password = Runtime.EncryptionKey; + private readonly SaveFilter _saveFilter; + + public SqlConnectionsSaver(SaveFilter saveFilter) + { + if (saveFilter == null) + throw new ArgumentNullException(nameof(saveFilter)); + _saveFilter = saveFilter; + } + + public void Save(ConnectionTreeModel connectionTreeModel) + { + using (var sqlConnector = DatabaseConnectorFactory.SqlDatabaseConnectorFromSettings()) + { + sqlConnector.Connect(); + var databaseVersionVerifier = new SqlDatabaseVersionVerifier(sqlConnector); + + if (!databaseVersionVerifier.VerifyDatabaseVersion()) + { + Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strErrorConnectionListSaveFailed); + return; + } + + var rootTreeNode = connectionTreeModel.RootNodes.OfType().First(); + + UpdateRootNodeTable(rootTreeNode, sqlConnector); + UpdateConnectionsTable(rootTreeNode, sqlConnector); + UpdateUpdatesTable(sqlConnector); + } + } + + private void UpdateRootNodeTable(RootNodeInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector) + { + var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); + string strProtected; + if (rootTreeNode != null) + { + if (rootTreeNode.Password) + { + _password = rootTreeNode.PasswordString.ConvertToSecureString(); + strProtected = cryptographyProvider.Encrypt("ThisIsProtected", _password); + } + else + { + strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", _password); + } + } + else + { + strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", _password); + } + + var sqlQuery = new SqlCommand("DELETE FROM tblRoot", sqlDatabaseConnector.SqlConnection); + sqlQuery.ExecuteNonQuery(); + + if (rootTreeNode != null) + { + sqlQuery = + new SqlCommand( + "INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES(\'" + + MiscTools.PrepareValueForDB(rootTreeNode.Name) + "\', 0, \'" + strProtected + "\'," + + ConnectionsFileInfo.ConnectionFileVersion.ToString(CultureInfo.InvariantCulture) + ")", + sqlDatabaseConnector.SqlConnection); + sqlQuery.ExecuteNonQuery(); + } + else + { + Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, $"UpdateRootNodeTable: rootTreeNode was null. Could not insert!"); + } + } + + private void UpdateConnectionsTable(ContainerInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector) + { + var sqlQuery = new SqlCommand("DELETE FROM tblCons", sqlDatabaseConnector.SqlConnection); + sqlQuery.ExecuteNonQuery(); + var serializer = new DataTableSerializer(_saveFilter); + var dataTable = serializer.Serialize(rootTreeNode); + var dataProvider = new SqlDataProvider(sqlDatabaseConnector); + dataProvider.Save(dataTable); + } + + private void UpdateUpdatesTable(SqlDatabaseConnector sqlDatabaseConnector) + { + var sqlQuery = new SqlCommand("DELETE FROM tblUpdate", sqlDatabaseConnector.SqlConnection); + sqlQuery.ExecuteNonQuery(); + sqlQuery = new SqlCommand("INSERT INTO tblUpdate (LastUpdate) VALUES(\'" + MiscTools.DBDate(DateTime.Now) + "\')", sqlDatabaseConnector.SqlConnection); + sqlQuery.ExecuteNonQuery(); + } + } +} diff --git a/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs b/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs new file mode 100644 index 000000000..79a53c9fa --- /dev/null +++ b/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using mRemoteNG.App; +using mRemoteNG.Config.DataProviders; +using mRemoteNG.Config.Serializers; +using mRemoteNG.Security; +using mRemoteNG.Security.Factories; +using mRemoteNG.Tree; +using mRemoteNG.Tree.Root; + +namespace mRemoteNG.Config.Connections +{ + public class XmlConnectionsSaver : ISaver + { + private readonly string _connectionFileName; + private readonly SaveFilter _saveFilter; + + public XmlConnectionsSaver(string connectionFileName, SaveFilter saveFilter) + { + if (string.IsNullOrEmpty(connectionFileName)) + throw new ArgumentException($"Argument '{nameof(connectionFileName)}' cannot be null or empty"); + if (saveFilter == null) + throw new ArgumentNullException(nameof(saveFilter)); + + _connectionFileName = connectionFileName; + _saveFilter = saveFilter; + } + + public void Save(ConnectionTreeModel connectionTreeModel) + { + try + { + var cryptographyProvider = new CryptoProviderFactoryFromSettings().Build(); + var connectionNodeSerializer = new XmlConnectionNodeSerializer26( + cryptographyProvider, + connectionTreeModel.RootNodes.OfType().First().PasswordString.ConvertToSecureString(), + _saveFilter); + var xmlConnectionsSerializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer) + { + UseFullEncryption = mRemoteNG.Settings.Default.EncryptCompleteConnectionsFile + }; + var xml = xmlConnectionsSerializer.Serialize(connectionTreeModel); + + var fileDataProvider = new FileDataProviderWithRollingBackup(_connectionFileName); + fileDataProvider.Save(xml); + } + catch (Exception ex) + { + Runtime.MessageCollector?.AddExceptionStackTrace("SaveToXml failed", ex); + } + } + } +} diff --git a/mRemoteV1/Connection/ConnectionsService.cs b/mRemoteV1/Connection/ConnectionsService.cs index 0dedb6c7f..c7a4b0755 100644 --- a/mRemoteV1/Connection/ConnectionsService.cs +++ b/mRemoteV1/Connection/ConnectionsService.cs @@ -6,6 +6,7 @@ using mRemoteNG.App.Info; using mRemoteNG.Config.Connections; using mRemoteNG.Config.Putty; using mRemoteNG.Connection.Protocol; +using mRemoteNG.Security; using mRemoteNG.Tree; using mRemoteNG.Tree.Root; @@ -14,12 +15,11 @@ namespace mRemoteNG.Connection public class ConnectionsService { private readonly PuttySessionsManager _puttySessionsManager; - private readonly ConnectionsLoader _connectionsLoader; public bool IsConnectionsFileLoaded { get; set; } public bool UsingDatabase { get; private set; } public string ConnectionFileName { get; private set; } - public static DateTime LastSqlUpdate { get; private set; } + public DateTime LastSqlUpdate { get; set; } public ConnectionTreeModel ConnectionTreeModel { @@ -31,27 +31,19 @@ namespace mRemoteNG.Connection { if (puttySessionsManager == null) throw new ArgumentNullException(nameof(puttySessionsManager)); + _puttySessionsManager = puttySessionsManager; - _connectionsLoader = new ConnectionsLoader(); } public void NewConnectionsFile(string filename) { try { - UpdateCustomConsPathSetting(filename); - var newConnectionsModel = new ConnectionTreeModel(); newConnectionsModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection)); - var connectionSaver = new ConnectionsSaver - { - ConnectionTreeModel = newConnectionsModel, - ConnectionFileName = filename, - SaveFilter = new Security.SaveFilter() - }; - connectionSaver.SaveConnections(); - + SaveConnections(newConnectionsModel, false, new SaveFilter(), filename); LoadConnections(false, false, filename); + UpdateCustomConsPathSetting(filename); } catch (Exception ex) { @@ -103,7 +95,19 @@ namespace mRemoteNG.Connection { var oldConnectionTreeModel = ConnectionTreeModel; var oldIsUsingDatabaseValue = UsingDatabase; - var newConnectionTreeModel = _connectionsLoader.LoadConnections(useDatabase, import, connectionFileName); + + var newConnectionTreeModel = + (useDatabase + ? new SqlConnectionsLoader().Load() + : new XmlConnectionsLoader(connectionFileName).Load()) + ?? new ConnectionTreeModel(); + + if (!import) + { + _puttySessionsManager.AddSessions(); + newConnectionTreeModel.RootNodes.AddRange(_puttySessionsManager.RootPuttySessionsNodes); + } + IsConnectionsFileLoaded = true; ConnectionFileName = connectionFileName; UsingDatabase = useDatabase; @@ -112,6 +116,66 @@ namespace mRemoteNG.Connection return newConnectionTreeModel; } + /// + /// Saves the currently loaded with + /// no . + /// + public void SaveConnections() + { + if (!IsConnectionsFileLoaded) + return; + SaveConnections(ConnectionTreeModel, UsingDatabase, new SaveFilter(), ConnectionFileName); + } + + /// + /// Saves the given . + /// If is true, is ignored + /// + /// + /// + /// + /// + public void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName) + { + if (connectionTreeModel == null) return; + + try + { + Runtime.RemoteConnectionsSyncronizer?.Disable(); + + var previouslyUsingDatabase = UsingDatabase; + if (useDatabase) + new SqlConnectionsSaver(saveFilter).Save(connectionTreeModel); + else + new XmlConnectionsSaver(connectionFileName, saveFilter).Save(connectionTreeModel); + + if (UsingDatabase) + LastSqlUpdate = DateTime.Now; + + UsingDatabase = useDatabase; + ConnectionFileName = connectionFileName; + RaiseConnectionsSavedEvent(connectionTreeModel, previouslyUsingDatabase, UsingDatabase, connectionFileName); + } + catch (Exception ex) + { + Runtime.MessageCollector?.AddExceptionMessage(string.Format(Language.strConnectionsFileCouldNotSaveAs, connectionFileName), ex, logOnly:false); + } + finally + { + Runtime.RemoteConnectionsSyncronizer?.Enable(); + } + } + + public string GetStartupConnectionFileName() + { + return Settings.Default.LoadConsFromCustomLocation == false ? GetDefaultStartupConnectionFileName() : Settings.Default.CustomConsPath; + } + + public string GetDefaultStartupConnectionFileName() + { + return Runtime.IsPortableEdition ? GetDefaultStartupConnectionFileNamePortableEdition() : GetDefaultStartupConnectionFileNameNormalEdition(); + } + private void UpdateCustomConsPathSetting(string filename) { if (filename == GetDefaultStartupConnectionFileName()) @@ -125,16 +189,6 @@ namespace mRemoteNG.Connection } } - public string GetStartupConnectionFileName() - { - return Settings.Default.LoadConsFromCustomLocation == false ? GetDefaultStartupConnectionFileName() : Settings.Default.CustomConsPath; - } - - public string GetDefaultStartupConnectionFileName() - { - return Runtime.IsPortableEdition ? GetDefaultStartupConnectionFileNamePortableEdition() : GetDefaultStartupConnectionFileNameNormalEdition(); - } - private string GetDefaultStartupConnectionFileNameNormalEdition() { var appDataPath = Path.Combine( @@ -151,8 +205,9 @@ namespace mRemoteNG.Connection #region Events public event EventHandler ConnectionsLoaded; + public event EventHandler ConnectionsSaved; - protected virtual void RaiseConnectionsLoadedEvent(ConnectionTreeModel previousTreeModel, ConnectionTreeModel newTreeModel, + private void RaiseConnectionsLoadedEvent(ConnectionTreeModel previousTreeModel, ConnectionTreeModel newTreeModel, bool previousSourceWasDatabase, bool newSourceIsDatabase, string newSourcePath) { @@ -163,6 +218,11 @@ namespace mRemoteNG.Connection newSourceIsDatabase, newSourcePath)); } + + private void RaiseConnectionsSavedEvent(ConnectionTreeModel modelThatWasSaved, bool previouslyUsingDatabase, bool usingDatabase, string connectionFileName) + { + ConnectionsSaved?.Invoke(this, new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase, usingDatabase, connectionFileName)); + } #endregion } } \ No newline at end of file diff --git a/mRemoteV1/UI/Forms/ExportForm.cs b/mRemoteV1/UI/Forms/ExportForm.cs index 01faa7dac..8b2e8a78c 100644 --- a/mRemoteV1/UI/Forms/ExportForm.cs +++ b/mRemoteV1/UI/Forms/ExportForm.cs @@ -26,12 +26,12 @@ namespace mRemoteNG.UI.Forms } } - public ConnectionsSaver.Format SaveFormat + public SaveFormat SaveFormat { get { var exportFormat = cboFileFormat.SelectedItem as ExportFormat; - return exportFormat?.Format ?? ConnectionsSaver.Format.mRXML; + return exportFormat?.Format ?? SaveFormat.mRXML; } set { @@ -173,8 +173,8 @@ namespace mRemoteNG.UI.Forms private void ExportForm_Load(object sender, EventArgs e) { cboFileFormat.Items.Clear(); - cboFileFormat.Items.Add(new ExportFormat(ConnectionsSaver.Format.mRXML)); - cboFileFormat.Items.Add(new ExportFormat(ConnectionsSaver.Format.mRCSV)); + cboFileFormat.Items.Add(new ExportFormat(SaveFormat.mRXML)); + cboFileFormat.Items.Add(new ExportFormat(SaveFormat.mRCSV)); cboFileFormat.SelectedIndex = 0; ApplyTheme(); ThemeManager.getInstance().ThemeChanged += ApplyTheme; @@ -211,7 +211,7 @@ namespace mRemoteNG.UI.Forms private void SelectFileTypeBasedOnSaveFormat(FileDialog saveFileDialog) { - saveFileDialog.FilterIndex = SaveFormat == ConnectionsSaver.Format.mRCSV ? 2 : 1; + saveFileDialog.FilterIndex = SaveFormat == SaveFormat.mRCSV ? 2 : 1; } private void btnOK_Click(object sender, EventArgs e) @@ -226,7 +226,7 @@ namespace mRemoteNG.UI.Forms private void cboFileformat_SelectedIndexChanged(object sender, EventArgs e) { - if (SaveFormat == ConnectionsSaver.Format.mRXML) + if (SaveFormat == SaveFormat.mRXML) { chkUsername.Enabled = false; chkPassword.Enabled = false; @@ -296,12 +296,12 @@ namespace mRemoteNG.UI.Forms { #region Public Properties - public ConnectionsSaver.Format Format { get; } + public SaveFormat Format { get; } #endregion #region Constructors - public ExportFormat(ConnectionsSaver.Format format) + public ExportFormat(SaveFormat format) { Format = format; } @@ -312,9 +312,9 @@ namespace mRemoteNG.UI.Forms { switch (Format) { - case ConnectionsSaver.Format.mRXML: + case SaveFormat.mRXML: return Language.strMremoteNgXml; - case ConnectionsSaver.Format.mRCSV: + case SaveFormat.mRCSV: return Language.strMremoteNgCsv; default: return Format.ToString(); diff --git a/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs b/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs index dbfa18411..75dde6f27 100644 --- a/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs +++ b/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs @@ -69,7 +69,6 @@ namespace mRemoteNG.UI.Forms.OptionsPages private static void ReinitializeSqlUpdater() { Runtime.RemoteConnectionsSyncronizer?.Dispose(); - FrmMain.Default.AreWeUsingSqlServerForSavingConnections = Settings.Default.UseSQLServer; if (Settings.Default.UseSQLServer) { @@ -80,6 +79,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages { Runtime.RemoteConnectionsSyncronizer?.Dispose(); Runtime.RemoteConnectionsSyncronizer = null; + Runtime.LoadConnections(true); } } diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index fbf9c8aa3..545117a15 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -449,7 +449,7 @@ namespace mRemoteNG.UI.Forms if (Runtime.ConnectionsService.IsConnectionsFileLoaded) { - if (AreWeUsingSqlServerForSavingConnections) + if (Runtime.ConnectionsService.UsingDatabase) { titleBuilder.Append(separator); titleBuilder.Append(Language.strSQLServer.TrimEnd(':')); diff --git a/mRemoteV1/UI/Menu/MainFileMenu.cs b/mRemoteV1/UI/Menu/MainFileMenu.cs index dc81220d7..422861f39 100644 --- a/mRemoteV1/UI/Menu/MainFileMenu.cs +++ b/mRemoteV1/UI/Menu/MainFileMenu.cs @@ -6,7 +6,9 @@ using mRemoteNG.App; using mRemoteNG.App.Info; using mRemoteNG.Connection; using mRemoteNG.Container; +using mRemoteNG.Security; using mRemoteNG.Tree; +using mRemoteNG.UI.Forms; using mRemoteNG.UI.Window; namespace mRemoteNG.UI.Menu @@ -358,7 +360,7 @@ namespace mRemoteNG.UI.Menu switch (msgBoxResult) { case DialogResult.Yes: - Runtime.SaveConnections(); + Runtime.ConnectionsService.SaveConnections(); break; case DialogResult.Cancel: return; @@ -375,7 +377,29 @@ namespace mRemoteNG.UI.Menu private void mMenFileSaveAs_Click(object sender, EventArgs e) { - Runtime.SaveConnectionsAs(); + using (var saveFileDialog = new SaveFileDialog()) + { + saveFileDialog.CheckPathExists = true; + saveFileDialog.InitialDirectory = ConnectionsFileInfo.DefaultConnectionsPath; + saveFileDialog.FileName = ConnectionsFileInfo.DefaultConnectionsFile; + saveFileDialog.OverwritePrompt = true; + saveFileDialog.Filter = $@"{Language.strFiltermRemoteXML}|*.xml|{Language.strFilterAll}|*.*"; + + if (saveFileDialog.ShowDialog(FrmMain.Default) != DialogResult.OK) return; + var newFileName = saveFileDialog.FileName; + + Runtime.ConnectionsService.SaveConnections(Runtime.ConnectionsService.ConnectionTreeModel, false, new SaveFilter(), newFileName); + + if (newFileName == Runtime.ConnectionsService.GetDefaultStartupConnectionFileName()) + { + Settings.Default.LoadConsFromCustomLocation = false; + } + else + { + Settings.Default.LoadConsFromCustomLocation = true; + Settings.Default.CustomConsPath = newFileName; + } + } } private void mMenFileDelete_Click(object sender, EventArgs e) diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index f248378fe..f040081e4 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -134,12 +134,17 @@ + + + + + @@ -175,8 +180,6 @@ - -