diff --git a/Duplicati/CommandLine/ServerUtil/Connection.cs b/Duplicati/CommandLine/ServerUtil/Connection.cs index e6ba08138..39346d029 100644 --- a/Duplicati/CommandLine/ServerUtil/Connection.cs +++ b/Duplicati/CommandLine/ServerUtil/Connection.cs @@ -193,58 +193,62 @@ public class Connection } // If we can read the server database, try to create a signin token - try + // But don't try to look at the database if the password is already set. + if (string.IsNullOrWhiteSpace(settings.Password)) { - var opts = new Dictionary(); - if (settings.Key != null) - opts["settings-encryption-key"] = settings.Key.Key; - if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly), DataFolderManager.SERVER_DATABASE_FILENAME))) + try { - string? cfg = null; - using (var connection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), opts, true)) + var opts = new Dictionary(); + if (settings.Key != null) + opts["settings-encryption-key"] = settings.Key.Key; + if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly), DataFolderManager.SERVER_DATABASE_FILENAME))) { - cfg = connection.ApplicationSettings.JWTConfig; - if (settings.HostUrl.Scheme == "https" && connection.ApplicationSettings.ServerSSLCertificate != null && trustedCertificateHashes.Count == 0) + string? cfg = null; + using (var connection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), opts, true, false)) { - var selfSignedCertHash = connection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString(); - if (!string.IsNullOrWhiteSpace(selfSignedCertHash)) - trustedCertificateHashes.Add(selfSignedCertHash); + cfg = connection.ApplicationSettings.JWTConfig; + if (settings.HostUrl.Scheme == "https" && connection.ApplicationSettings.ServerSSLCertificate != null && trustedCertificateHashes.Count == 0) + { + var selfSignedCertHash = connection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString(); + if (!string.IsNullOrWhiteSpace(selfSignedCertHash)) + trustedCertificateHashes.Add(selfSignedCertHash); + } + } + + if (!string.IsNullOrWhiteSpace(cfg)) + { + var signinjwt = new JWTTokenProvider( + JsonSerializer.Deserialize(cfg) + ?? throw new InvalidOperationException("Failed to deserialize JWTConfig") + ).CreateSigninToken("server-cli"); + + var responseTask = client.PostAsync("auth/signin", JsonContent.Create(new { SigninToken = signinjwt, RememberMe = obtainRefreshToken })); + var (accessToken, refreshToken) = await ParseAuthResponse(responseTask); + if (string.IsNullOrWhiteSpace(accessToken)) + throw new InvalidOperationException("Failed to get access token"); + + if (!string.IsNullOrWhiteSpace(refreshToken)) + (settings with { RefreshToken = refreshToken }).Save(console); + + return CreateConnectionWithClient(client, accessToken); } } - - if (!string.IsNullOrWhiteSpace(cfg)) + else if (!string.IsNullOrWhiteSpace(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly))) { - var signinjwt = new JWTTokenProvider( - JsonSerializer.Deserialize(cfg) - ?? throw new InvalidOperationException("Failed to deserialize JWTConfig") - ).CreateSigninToken("server-cli"); - - var responseTask = client.PostAsync("auth/signin", JsonContent.Create(new { SigninToken = signinjwt, RememberMe = obtainRefreshToken })); - var (accessToken, refreshToken) = await ParseAuthResponse(responseTask); - if (string.IsNullOrWhiteSpace(accessToken)) - throw new InvalidOperationException("Failed to get access token"); - - if (!string.IsNullOrWhiteSpace(refreshToken)) - (settings with { RefreshToken = refreshToken }).Save(console); - - return CreateConnectionWithClient(client, accessToken); + if (console != null) + console.AppendConsoleMessage($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}"); + else + Console.WriteLine($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}"); } } - else if (!string.IsNullOrWhiteSpace(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly))) + catch (Exception ex) { if (console != null) - console.AppendConsoleMessage($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}"); + console.AppendConsoleMessage($"Failed to obtain a signin token: {ex.Message}"); else - Console.WriteLine($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}"); + Console.WriteLine($"Failed to obtain a signin token: {ex.Message}"); } } - catch (Exception ex) - { - if (console != null) - console.AppendConsoleMessage($"Failed to obtain a signin token: {ex.Message}"); - else - Console.WriteLine($"Failed to obtain a signin token: {ex.Message}"); - } // Otherwise, we need a password to log in try diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs index 0cfc87e6c..e09a0461a 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs @@ -192,7 +192,7 @@ namespace Duplicati.GUI.TrayIcon if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ReadWritePermissionSet), DataFolderManager.SERVER_DATABASE_FILENAME))) { passwordSource = PasswordSource.Database; - databaseConnection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), options, true); + databaseConnection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), options, true, false); if (databaseConnection != null) { diff --git a/Duplicati/Library/AutoUpdater/DataFolderManager.cs b/Duplicati/Library/AutoUpdater/DataFolderManager.cs index 09ef527f2..1b4f1c024 100644 --- a/Duplicati/Library/AutoUpdater/DataFolderManager.cs +++ b/Duplicati/Library/AutoUpdater/DataFolderManager.cs @@ -25,6 +25,7 @@ using System; using System.IO; using System.Linq; using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; using Duplicati.Library.Utility; namespace Duplicati.Library.AutoUpdater; @@ -95,9 +96,9 @@ public static class DataFolderManager { // Trigger portable mode, if the flag is set PORTABLE_MODE = ParseBoolSlim(ExtractOptionSlim(PORTABLE_MODE_OPTION)); - + string dataFolder = string.Empty; - + // The environment variable is a legacy setting var envOverride = Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME); @@ -142,17 +143,23 @@ public static class DataFolderManager Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message); } } - else + else if (mode == AccessMode.ReadWritePermissionSet) { - Directory.CreateDirectory(dataFolder); try { - if (mode == AccessMode.ReadWritePermissionSet) + Directory.CreateDirectory(dataFolder); + try + { SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(dataFolder); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message); + } } catch (Exception ex) { - Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message); + throw new UserInformationException($"Failed to create data folder {dataFolder}", "FailedToCreateDataFolder", ex); } } @@ -167,7 +174,7 @@ public static class DataFolderManager if (mode == AccessMode.ReadWritePermissionSet && !File.Exists(Path.Combine(dataFolder, MACHINE_FILE))) File.WriteAllText(Path.Combine(dataFolder, MACHINE_FILE), AutoUpdateSettings.UpdateMachineFileText(InstallID)); - + return dataFolder; } @@ -227,7 +234,7 @@ public static class DataFolderManager && !value.Equals("no", StringComparison.OrdinalIgnoreCase) && !value.Equals("off", StringComparison.OrdinalIgnoreCase); } - + /// /// The unique machine installation ID /// diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index aa86179da..8740bec42 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -197,7 +197,7 @@ namespace Duplicati.Server ISchedulerService scheduler = null; try { - var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole); + var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole, true); if (!connection.ApplicationSettings.FixedInvalidBackupId) connection.FixInvalidBackupId(); @@ -736,7 +736,7 @@ namespace Duplicati.Server throw new Exception("Server invoked with --help"); } - public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary commandlineOptions, bool silentConsole) + public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary commandlineOptions, bool silentConsole, bool changeDbEncryption) { // Emit a warning if the database is stored in the Windows folder if (Util.IsPathUnderWindowsFolder(applicationSettings.DataFolder)) @@ -786,29 +786,26 @@ namespace Duplicati.Server if (requireDbEncryptionKey && !(hasValidEncryptionKey || disableDbEncryption)) throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey"); - if (!hasValidEncryptionKey) + var hasEncryptedFields = false; + try { - try - { - var hasEncryptedFields = false; - using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId")) - hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd - .SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS) - .SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false); + using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId")) + hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd + .SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS) + .SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false); - if (hasEncryptedFields) - { - Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); - if (!silentConsole) - Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); - } - } - catch + if (hasEncryptedFields && !hasValidEncryptionKey) { - // Ignore errors here, as we are just checking for a potential issue - // Only negative effect is that we do not show a potentially helpful warning + Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); + if (!silentConsole) + Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); } } + catch + { + // Ignore errors here, as we are just checking for a potential issue + // Only negative effect is that we do not show a potentially helpful warning + } if (!hasValidEncryptionKey && !disableDbEncryption) { @@ -826,6 +823,11 @@ namespace Duplicati.Server Console.WriteLine(Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); } + // If the database is not encrypted, and we are not changing the encryption + // don't pass the key, as that would cause the database to be encrypted + if (!hasEncryptedFields && !changeDbEncryption && hasValidEncryptionKey) + encKey = null; + return new Connection(con, disableDbEncryption, encKey, applicationSettings.DataFolder, applicationSettings.StartOrStopUsageReporter); } diff --git a/Duplicati/UnitTest/ImportExportTests.cs b/Duplicati/UnitTest/ImportExportTests.cs index 527e3a4fb..64ba12bd0 100644 --- a/Duplicati/UnitTest/ImportExportTests.cs +++ b/Duplicati/UnitTest/ImportExportTests.cs @@ -109,7 +109,7 @@ namespace Duplicati.UnitTest } byte[] jsonByteArray; - using (var con = Program.GetDatabaseConnection(new ApplicationSettings(), advancedOptions, true)) + using (var con = Program.GetDatabaseConnection(new ApplicationSettings(), advancedOptions, true, false)) jsonByteArray = BackupImportExportHandler.ExportToJSON(con, backup, null); // The username should not have the '%40' converted to '@' since the import code