diff --git a/Duplicati/Library/Main/Operation/Restore/Channels.cs b/Duplicati/Library/Main/Operation/Restore/Channels.cs index 4d14ee055..9b83773da 100644 --- a/Duplicati/Library/Main/Operation/Restore/Channels.cs +++ b/Duplicati/Library/Main/Operation/Restore/Channels.cs @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Threading.Tasks; using CoCoL; using Duplicati.Library.Main.Volumes; using Duplicati.Library.Utility; @@ -46,12 +45,12 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// Channel between and . /// - public readonly IChannel<(long, Task)> DownloadRequest = ChannelManager.CreateChannel<(long, Task)>(buffersize: BufferSize); + public readonly IChannel DownloadRequest = ChannelManager.CreateChannel(buffersize: BufferSize); /// /// Channel between and /// - public readonly IChannel<(long, TempFile)> DecryptRequest = ChannelManager.CreateChannel<(long, TempFile)>(buffersize: BufferSize); + public readonly IChannel<(long, string, TempFile)> DecryptRequest = ChannelManager.CreateChannel<(long, string, TempFile)>(buffersize: BufferSize); /// /// Channel between and diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs index 2dc5fa8e8..59576fac9 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs @@ -21,6 +21,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using CoCoL; using Duplicati.Library.Main.Volumes; @@ -57,15 +58,23 @@ namespace Duplicati.Library.Main.Operation.Restore Stopwatch sw_decrypt = options.InternalProfiling ? new() : null; try { + // Taken from BackendManager.GetOperation + using var encryption = options.NoEncryption + ? null + : (DynamicLoader.EncryptionLoader.GetModule(options.EncryptionModule, options.Passphrase, options.RawOptions) + ?? throw new Exception(Strings.BackendMananger.EncryptionModuleNotFound(options.EncryptionModule)) + ); + while (true) { // Get the block request and volume from the `VolumeDownloader` process. sw_read?.Start(); - var (volume_id, volume) = await self.Input.ReadAsync(); + var (volume_id, volume_name, volume) = await self.Input.ReadAsync().ConfigureAwait(false); sw_read?.Stop(); sw_decrypt?.Start(); - var bvr = new BlockVolumeReader(options.CompressionModule, volume, options); + var tmpfile = DecryptFile(volume, DetectEncryptionModule(volume_name, options, encryption)); + var bvr = new BlockVolumeReader(options.CompressionModule, tmpfile, options); sw_decrypt?.Stop(); sw_write?.Start(); @@ -90,6 +99,83 @@ namespace Duplicati.Library.Main.Operation.Restore } }); } + + // Taken from BackendManager.GetOperation + public static Library.Utility.TempFile DecryptFile(Library.Utility.TempFile tempFile, Interface.IEncryption? decrypter) + { + // Support no encryption + if (decrypter == null) + return tempFile; + + Library.Utility.TempFile? decryptTarget = null; + + // Always dispose the source file + using (tempFile) + using (new Logging.Timer(LOGTAG, "DecryptFile", "Decrypting " + tempFile)) + { + try + { + decryptTarget = new Library.Utility.TempFile(); + try { decrypter.Decrypt(tempFile, decryptTarget); } + // If we fail here, make sure that we throw a crypto exception + catch (System.Security.Cryptography.CryptographicException) { throw; } + catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } + + var result = decryptTarget; + decryptTarget = null; + return result; + } + finally + { + // Remove temp files on failure + decryptTarget?.Dispose(); + } + } + } + + private static Interface.IEncryption? DetectEncryptionModule(string remotefilename, Options options, Interface.IEncryption? encryption) + { + try + { + // Auto-guess the encryption module + var ext = (System.IO.Path.GetExtension(remotefilename) ?? "").TrimStart('.'); + if (!ext.Equals(encryption?.FilenameExtension, StringComparison.OrdinalIgnoreCase)) + { + // Check if the file is not encrypted + if (DynamicLoader.CompressionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) + { + if (encryption != null) + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, options.EncryptionModule); + return null; + } + // Check if the file is encrypted with something else + else if (DynamicLoader.EncryptionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use matching encryption module", ext, options.EncryptionModule); + + try + { + return DynamicLoader.EncryptionLoader.GetModule(ext, options.Passphrase, options.RawOptions) + ?? encryption; + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "AutomaticDecryptionDetection", ex, "Failed to load encryption module \"{0}\", using specified encryption module \"{1}\"", ext, options.EncryptionModule); + } + } + // Fallback, lets see what happens... + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, options.EncryptionModule); + } + } + + return encryption; + } + // If we fail here, make sure that we throw a crypto exception + catch (System.Security.Cryptography.CryptographicException) { throw; } + catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } + } } } \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs index 40a28d772..65c20c92a 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs @@ -22,8 +22,10 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using CoCoL; +using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; using Duplicati.Library.Utility; @@ -43,9 +45,12 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// Runs the volume downloader process. /// + /// The named channels for the restore operation. + /// The local restore database, used to find volume information given the volume ID. + /// The backend to use for downloading the volumes. /// The restore options. /// The restore results. - public static Task Run(Channels channels, LocalRestoreDatabase db, Options options, RestoreResults results) + public static Task Run(Channels channels, LocalRestoreDatabase db, IBackend backend, Options options, RestoreResults results) { return AutomationExtensions.RunTask( new @@ -65,19 +70,19 @@ namespace Duplicati.Library.Main.Operation.Restore { // Get the block request from the `BlockManager` process. sw_read?.Start(); - var (volume_id, waittask) = await self.Input.ReadAsync(); + var volume_id = await self.Input.ReadAsync().ConfigureAwait(false); sw_read?.Stop(); // Trigger the download. sw_wait?.Start(); - TempFile f = null; + TempFile f = new(); + var (volume_name, size, hash) = db.GetVolumeInfo(volume_id).First(); try { - f = await waittask.ConfigureAwait(false); + await backend.GetAsync(volume_name, f.Name, CancellationToken.None).ConfigureAwait(false); } catch (Exception) { - var (volume_name, _, _) = db.GetVolumeInfo(volume_id).First(); lock (results) results.BrokenRemoteFiles.Add(volume_name); @@ -87,7 +92,7 @@ namespace Duplicati.Library.Main.Operation.Restore // Pass the download handle (which may or may not have downloaded already) to the `VolumeDecryptor` process. sw_write?.Start(); - await self.Output.WriteAsync((volume_id, f)); + await self.Output.WriteAsync((volume_id, volume_name, f)).ConfigureAwait(false); sw_write?.Stop(); } } @@ -105,6 +110,10 @@ namespace Duplicati.Library.Main.Operation.Restore Logging.Log.WriteErrorMessage(LOGTAG, "DownloadError", ex, "Error during download"); throw; } + finally + { + backend.Dispose(); + } }); } } diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs index ba14a643d..0c636973c 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs @@ -20,7 +20,6 @@ // DEALINGS IN THE SOFTWARE. using CoCoL; -using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Volumes; using Duplicati.Library.Utility; using System; @@ -46,11 +45,9 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// Runs the volume manager process. /// - /// The local restore database, used to find the volume where a block is stored. - /// The backend manager, used to fetch the volumes from the backend. + /// The named channels for the restore operation. /// The restore options. - /// The restore results. - public static Task Run(Channels channels, LocalRestoreDatabase db, IBackendManager backend, Options options, RestoreResults results) + public static Task Run(Channels channels, Options options) { return AutomationExtensions.RunTask( new @@ -106,13 +103,7 @@ namespace Duplicati.Library.Main.Operation.Restore } else { - sw_query?.Start(); - var (volume_name, volume_size, volume_hash) = db.GetVolumeInfo(request.VolumeID).First(); - sw_query?.Stop(); - sw_backend?.Start(); - var handle = backend.GetAsync(volume_name, volume_hash, volume_size, results.TaskControl.TransferToken); - sw_backend?.Stop(); - await self.DownloadRequest.WriteAsync((request.VolumeID, handle)); + await self.DownloadRequest.WriteAsync(request.VolumeID).ConfigureAwait(false); in_flight[request.VolumeID] = [request]; } } diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index 86fd0030c..c119e47a3 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -339,8 +339,8 @@ namespace Duplicati.Library.Main.Operation var filelister = Restore.FileLister.Run(channels, database, m_options, m_result); var fileprocessors = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(i => Restore.FileProcessor.Run(channels, database, fileprocessor_requests[i], fileprocessor_responses[i], m_options, m_result)).ToArray(); var blockmanager = Restore.BlockManager.Run(channels, database, m_options, fileprocessor_requests, fileprocessor_responses); - var volumecache = Restore.VolumeManager.Run(channels, database, backendManager, m_options, m_result); - var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, m_options, m_result)).ToArray(); + var volumecache = Restore.VolumeManager.Run(channels, m_options); + var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, backendManager.GetBackend(), m_options, m_result)).ToArray(); var volumedecryptors = Enumerable.Range(0, m_options.RestoreVolumeDecryptors).Select(i => Restore.VolumeDecryptor.Run(channels, m_options)).ToArray(); var volumedecompressors = Enumerable.Range(0, m_options.RestoreVolumeDecompressors).Select(i => Restore.VolumeDecompressor.Run(channels, m_options)).ToArray();