Merge branch 'master' into concurrent_processing

# Conflicts:
#	Duplicati/Library/Main/Operation/BackupHandler.cs
This commit is contained in:
Kenneth Skovhede
2016-08-31 14:17:35 +02:00
44 changed files with 904 additions and 703 deletions
@@ -138,6 +138,10 @@
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@@ -182,6 +182,10 @@
<Project>{BB014EA5-CE2C-4444-8D30-38983A0E8553}</Project>
<Name>Duplicati.Library.UsageReporter</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Duplicati.snk" />
@@ -37,6 +37,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
@@ -150,8 +153,15 @@
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="help.txt" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
+100 -63
View File
@@ -5,6 +5,7 @@ using System.Linq;
using Duplicati.Library.Main;
using Duplicati.Library.Main.Volumes;
using Duplicati.Library.Utility;
using Newtonsoft.Json.Linq;
namespace Duplicati.CommandLine.RecoveryTool
{
@@ -48,7 +49,7 @@ namespace Duplicati.CommandLine.RecoveryTool
var rawlist = backend.List();
Console.WriteLine("Found {0} files", rawlist.Count);
Console.WriteLine("Found {0} files at remote storage", rawlist.Count);
var i = 0;
var downloaded = 0;
@@ -89,14 +90,26 @@ namespace Duplicati.CommandLine.RecoveryTool
bool reencrypt = Library.Utility.Utility.ParseBoolOption(options, "reencrypt");
bool reupload = Library.Utility.Utility.ParseBoolOption(options, "reupload");
foreach (var entry in remotefiles)
// Needs order (Files or Blocks) and Indexes as last because indexes content will be adjusted based on recompressed blocks
var files = remotefiles.Where(a => a.FileType == RemoteVolumeType.Files).ToArray();
var blocks = remotefiles.Where(a => a.FileType == RemoteVolumeType.Blocks).ToArray();
var indexes = remotefiles.Where(a => a.FileType == RemoteVolumeType.Index).ToArray();
remotefiles = files.Concat(blocks).ToArray().Concat(indexes).ToArray();
Console.WriteLine("Found {0} files which belongs to backup with prefix {1}", remotefiles.Count(), m_Options.Prefix);
foreach (var remoteFile in remotefiles)
{
try
{
Console.Write("{0}: {1}", i, entry.File.Name);
Console.Write("{0}/{1}: {2}", ++i, remotefiles.Count(), remoteFile.File.Name);
var local = Path.Combine(targetfolder, entry.File.Name);
if (entry.EncryptionModule != null)
var localFileSource = Path.Combine(targetfolder, remoteFile.File.Name);
string localFileTarget;
string localFileSourceEncryption = "";
if (remoteFile.EncryptionModule != null)
{
if (string.IsNullOrWhiteSpace(m_Options.Passphrase))
{
@@ -105,116 +118,142 @@ namespace Duplicati.CommandLine.RecoveryTool
continue;
}
local = local.Substring(0, local.Length - entry.EncryptionModule.Length - 1);
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(remoteFile.EncryptionModule, m_Options.Passphrase, options))
localFileSourceEncryption = m.FilenameExtension;
localFileSource = localFileSource.Substring(0, localFileSource.Length - localFileSourceEncryption.Length - 1);
}
if (entry.CompressionModule == target_compr_module)
{
Console.WriteLine(" - compression types are same");
continue;
}
string localNew;
if (entry.CompressionModule != null)
{
localNew = local.Substring(0, local.Length - entry.CompressionModule.Length - 1) + "." + target_compr_module;
if (File.Exists(localNew))
{
Console.WriteLine(" - target file already exist");
continue;
}
}
if (remoteFile.CompressionModule != null)
localFileTarget = localFileSource.Substring(0, localFileSource.Length - remoteFile.CompressionModule.Length - 1) + "." + target_compr_module;
else
{
Console.WriteLine(" - cannot detect compression type");
continue;
}
if ((!reencrypt && File.Exists(localFileTarget)) || (reencrypt && File.Exists(localFileTarget + "." + localFileSourceEncryption)))
{
Console.WriteLine(" - target file already exist");
continue;
}
if (File.Exists(local))
File.Delete(local);
if (File.Exists(localFileSource))
File.Delete(localFileSource);
Console.Write(" - downloading ({0})...", Library.Utility.Utility.FormatSizeString(entry.File.Size));
Console.Write(" - downloading ({0})...", Library.Utility.Utility.FormatSizeString(remoteFile.File.Size));
DateTime originLastWriteTime;
FileInfo destinationFileInfo;
using (var tf = new Library.Utility.TempFile())
using (var tf = new TempFile())
{
backend.Get(entry.File.Name, tf);
backend.Get(remoteFile.File.Name, tf);
originLastWriteTime = new FileInfo(tf).LastWriteTime;
downloaded++;
if (entry.EncryptionModule != null)
if (remoteFile.EncryptionModule != null)
{
Console.Write(" decrypting ...");
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(entry.EncryptionModule, m_Options.Passphrase, options))
using (var tf2 = new Library.Utility.TempFile())
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(remoteFile.EncryptionModule, m_Options.Passphrase, options))
using (var tf2 = new TempFile())
{
m.Decrypt(tf, tf2);
File.Copy(tf2, local);
File.Copy(tf2, localFileSource);
File.Delete(tf2);
}
}
else
File.Copy(tf, local);
File.Copy(tf, localFileSource);
File.Delete(tf);
destinationFileInfo = new FileInfo(local);
destinationFileInfo = new FileInfo(localFileSource);
destinationFileInfo.LastWriteTime = originLastWriteTime;
}
if (entry.CompressionModule != null)
if (remoteFile.CompressionModule != null)
{
Console.Write(" recompressing ...");
using (var cmOld = Library.DynamicLoader.CompressionLoader.GetModule(entry.CompressionModule, local, options))
using (var cmNew = Library.DynamicLoader.CompressionLoader.GetModule(target_compr_module, localNew, options))
//Recompressing from eg. zip to zip
if (localFileSource == localFileTarget)
{
File.Move(localFileSource, localFileSource + ".same");
localFileSource = localFileSource + ".same";
}
using (var cmOld = Library.DynamicLoader.CompressionLoader.GetModule(remoteFile.CompressionModule, localFileSource, options))
using (var cmNew = Library.DynamicLoader.CompressionLoader.GetModule(target_compr_module, localFileTarget, options))
foreach (var cmfile in cmOld.ListFiles(""))
{
string cmfileNew = cmfile;
if (entry.FileType == RemoteVolumeType.Index)
var cmFileVolume = VolumeBase.ParseFilename(cmfileNew);
if (remoteFile.FileType == RemoteVolumeType.Index && cmFileVolume != null && cmFileVolume.FileType == RemoteVolumeType.Blocks)
{
var cmFileVolume = Library.Main.Volumes.VolumeBase.ParseFilename(cmfileNew);
if (cmFileVolume != null)
{
cmfileNew = cmfileNew.Replace("." + cmFileVolume.CompressionModule, "." + target_compr_module);
if(!reencrypt)
cmfileNew = cmfileNew.Replace("." + cmFileVolume.EncryptionModule, "");
}
}
// Correct inner filename extension to target compression type
cmfileNew = cmfileNew.Replace("." + cmFileVolume.CompressionModule, "." + target_compr_module);
if (!reencrypt)
cmfileNew = cmfileNew.Replace("." + cmFileVolume.EncryptionModule, "");
using (var sourceStream = cmOld.OpenRead(cmfile))
using (var cs = cmNew.CreateFile(cmfileNew, Duplicati.Library.Interface.CompressionHint.Compressible, cmOld.GetLastWriteTime(cmfile)))
//Because compression changes blocks file sizes - needs to be updated
string textJSON;
using (var sourceStream = cmOld.OpenRead(cmfile))
using (var sourceStreamReader = new StreamReader(sourceStream))
{
textJSON = sourceStreamReader.ReadToEnd();
JToken token = JObject.Parse(textJSON);
var fileInfoBlocks = new FileInfo(Path.Combine(targetfolder, cmfileNew.Replace("vol/", "")));
var filehasher = System.Security.Cryptography.HashAlgorithm.Create(m_Options.FileHashAlgorithm);
using (var fileStream = fileInfoBlocks.Open(FileMode.Open))
{
fileStream.Position = 0;
token["volumehash"] = Convert.ToBase64String(filehasher.ComputeHash(fileStream));
fileStream.Close();
}
token["volumesize"] = fileInfoBlocks.Length;
textJSON = token.ToString();
}
using (var sourceStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(textJSON)))
using (var cs = cmNew.CreateFile(cmfileNew, Library.Interface.CompressionHint.Compressible, cmOld.GetLastWriteTime(cmfile)))
Library.Utility.Utility.CopyStream(sourceStream, cs);
}
else
{
using (var sourceStream = cmOld.OpenRead(cmfile))
using (var cs = cmNew.CreateFile(cmfileNew, Library.Interface.CompressionHint.Compressible, cmOld.GetLastWriteTime(cmfile)))
Library.Utility.Utility.CopyStream(sourceStream, cs);
}
}
File.Delete(local);
destinationFileInfo = new FileInfo(localNew);
File.Delete(localFileSource);
destinationFileInfo = new FileInfo(localFileTarget);
destinationFileInfo.LastWriteTime = originLastWriteTime;
}
if (reencrypt && entry.EncryptionModule != null)
if (reencrypt && remoteFile.EncryptionModule != null)
{
Console.Write(" reencrypting ...");
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(entry.EncryptionModule, m_Options.Passphrase, options))
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(remoteFile.EncryptionModule, m_Options.Passphrase, options))
{
m.Encrypt(localNew, localNew + "." + m.FilenameExtension);
File.Delete(localNew);
localNew = localNew + "." + m.FilenameExtension;
m.Encrypt(localFileTarget, localFileTarget + "." + localFileSourceEncryption);
File.Delete(localFileTarget);
localFileTarget = localFileTarget + "." + localFileSourceEncryption;
}
destinationFileInfo = new FileInfo(localNew);
destinationFileInfo = new FileInfo(localFileTarget);
destinationFileInfo.LastWriteTime = originLastWriteTime;
}
if (reupload)
{
backend.Put((new FileInfo(localNew)).Name, localNew);
backend.Delete(entry.File.Name);
File.Delete(localNew);
Console.Write(" reuploading ...");
backend.Put((new FileInfo(localFileTarget)).Name, localFileTarget);
backend.Delete(remoteFile.File.Name);
File.Delete(localFileTarget);
}
Console.WriteLine(" done!");
@@ -225,8 +264,6 @@ namespace Duplicati.CommandLine.RecoveryTool
Console.WriteLine(" error: {0}", ex.ToString());
errors++;
}
i++;
}
if (reupload)
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
</packages>
@@ -307,6 +307,10 @@
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
@@ -27,7 +27,7 @@ namespace Duplicati.Library.Backend.Backblaze
private readonly string m_credentials;
private AuthResponse m_config;
private DateTime m_configExpires;
private const string AUTH_URL = "https://api.backblaze.com/b2api/v1/b2_authorize_account";
private const string AUTH_URL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_account";
public B2AuthHelper(string userid, string password)
: base()
@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -31,6 +31,6 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// [assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyFileVersion("2.0.0.7")]
@@ -31,6 +31,8 @@ namespace Duplicati.Library.Backend
{
public const string SSH_KEYFILE_OPTION = "ssh-keyfile";
public const string SSH_KEYFILE_INLINE = "ssh-key";
public const string SSH_FINGERPRINT_OPTION = "ssh-fingerprint";
public const string SSH_FINGERPRINT_ACCEPT_ANY_OPTION = "ssh-accept-any-fingerprints";
public const string KEYFILE_URI = "sshkey://";
Dictionary<string, string> m_options;
@@ -39,6 +41,8 @@ namespace Duplicati.Library.Backend
private string m_path;
private string m_username;
private string m_password;
private string m_fingerprint;
private bool m_fingerprintallowall;
private int m_port = 22;
@@ -59,10 +63,16 @@ namespace Duplicati.Library.Backend
m_username = options["auth-username"];
if (options.ContainsKey("auth-password"))
m_password = options["auth-password"];
if (options.ContainsKey(SSH_FINGERPRINT_OPTION))
m_fingerprint = options[SSH_FINGERPRINT_OPTION];
if (!string.IsNullOrEmpty(uri.Username))
m_username = uri.Username;
if (!string.IsNullOrEmpty(uri.Password))
m_password = uri.Password;
if (uri.QueryParameters != null && uri.QueryParameters[SSH_FINGERPRINT_OPTION] != null)
m_fingerprint = uri.QueryParameters[SSH_FINGERPRINT_OPTION];
m_fingerprintallowall = Utility.Utility.ParseBoolOption(options, SSH_FINGERPRINT_ACCEPT_ANY_OPTION);
m_path = uri.Path;
@@ -139,6 +149,7 @@ namespace Duplicati.Library.Backend
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.SSHv2Backend.DescriptionAuthPasswordShort, Strings.SSHv2Backend.DescriptionAuthPasswordLong),
new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.SSHv2Backend.DescriptionAuthUsernameShort, Strings.SSHv2Backend.DescriptionAuthUsernameLong),
new CommandLineArgument(SSH_FINGERPRINT_OPTION, CommandLineArgument.ArgumentType.String, Strings.SSHv2Backend.DescriptionFingerprintShort, Strings.SSHv2Backend.DescriptionFingerprintLong),
new CommandLineArgument(SSH_KEYFILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.SSHv2Backend.DescriptionSshkeyfileShort, Strings.SSHv2Backend.DescriptionSshkeyfileLong),
new CommandLineArgument(SSH_KEYFILE_INLINE, CommandLineArgument.ArgumentType.Password, Strings.SSHv2Backend.DescriptionSshkeyShort, Strings.SSHv2Backend.DescriptionSshkeyLong(KEYFILE_URI)),
});
@@ -231,6 +242,27 @@ namespace Duplicati.Library.Backend
else
con = new SftpClient(m_server, m_port, m_username, m_password);
con.HostKeyReceived += delegate (object sender, HostKeyEventArgs e)
{
e.CanTrust = false;
if (m_fingerprintallowall)
{
e.CanTrust = true;
return;
}
string hostFingerprint = e.HostKeyName + " " + e.KeyLength.ToString() + " " + BitConverter.ToString(e.FingerPrint).Replace('-', ':');
if (string.IsNullOrEmpty(m_fingerprint))
throw new Library.Utility.HostKeyException(Strings.SSHv2Backend.FingerprintNotSpecifiedManagedError(hostFingerprint.ToLower(), SSH_FINGERPRINT_OPTION, SSH_FINGERPRINT_ACCEPT_ANY_OPTION), hostFingerprint, m_fingerprint);
if (hostFingerprint.ToLower() != m_fingerprint.ToLower())
throw new Library.Utility.HostKeyException(Strings.SSHv2Backend.FingerprintNotMatchManagedError(hostFingerprint.ToLower()), hostFingerprint, m_fingerprint);
else
e.CanTrust = true;
};
con.Connect();
m_con = con;
@@ -261,7 +293,7 @@ namespace Duplicati.Library.Backend
public List<IFileEntry> List()
{
List<IFileEntry> files = new List<IFileEntry>();
var files = new List<IFileEntry>();
string path = ".";
+38 -34
View File
@@ -1,34 +1,38 @@
using Duplicati.Library.Localization.Short;
namespace Duplicati.Library.Backend.Strings {
internal static class KeyGenerator {
public static string Description { get { return LC.L(@"Module for generating SSH private/public keys"); } }
public static string DisplayName { get { return LC.L(@"SSH Key Generator"); } }
public static string KeyUsernameShort { get { return LC.L(@"Public key username"); } }
public static string KeyUsernameLong { get { return LC.L(@"A username to append to the public key"); } }
public static string KeyTypeShort { get { return LC.L(@"The key type"); } }
public static string KeyTypeLong { get { return LC.L(@"Determines the type of key to generate"); } }
public static string KeyLenShort { get { return LC.L(@"The key length"); } }
public static string KeyLenLong { get { return LC.L(@"The length of the key in bits"); } }
}
internal static class KeyUploader {
public static string Description { get { return LC.L(@"Module for uploading SSH public keys"); } }
public static string DisplayName { get { return LC.L(@"SSH Key Uploader"); } }
public static string UrlShort { get { return LC.L(@"The SSH connection URL"); } }
public static string UrlLong { get { return LC.L(@"The SSH connection URL used to establish the connection"); } }
public static string PubkeyShort { get { return LC.L(@"The SSH public key to append"); } }
public static string PubkeyLong { get { return LC.L(@"The SSH public key must be a valid SSH string, which is appended to the .ssh/authorized_keys file"); } }
}
internal static class SSHv2Backend {
public static string Description { get { return LC.L(@"This backend can read and write data to an SSH based backend, using SFTP. Allowed formats are ""ssh://hostname/folder"" or ""ssh://username:password@hostname/folder""."); } }
public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } }
public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supplies the password used to connect to the server"); } }
public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } }
public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supplies the username used to connect to the server"); } }
public static string DescriptionSshkeyfileLong { get { return LC.L(@"Points to a valid OpenSSH keyfile. If the file is encrypted, the password supplied is used to decrypt the keyfile. If this option is supplied, the password is not used to authenticate. This option only works when using the managed SSH client."); } }
public static string DescriptionSshkeyfileShort { get { return LC.L(@"Uses a SSH private key to authenticate"); } }
public static string DescriptionSshkeyLong(string urlprefix) { return LC.L(@"An url-encoded SSH private key. The private key must be prefixed with {0}. If the file is encrypted, the password supplied is used to decrypt the keyfile. If this option is supplied, the password is not used to authenticate. This option only works when using the managed SSH client.", urlprefix); }
public static string DescriptionSshkeyShort { get { return LC.L(@"Uses a SSH private key to authenticate"); } }
public static string DisplayName { get { return LC.L(@"SFTP (SSH)"); } }
public static string FolderNotFoundManagedError(string foldername, string message) { return LC.L(@"Unable to set folder to {0}, error message: {1}", foldername, message); }
}
}
using Duplicati.Library.Localization.Short;
namespace Duplicati.Library.Backend.Strings {
internal static class KeyGenerator {
public static string Description { get { return LC.L(@"Module for generating SSH private/public keys"); } }
public static string DisplayName { get { return LC.L(@"SSH Key Generator"); } }
public static string KeyUsernameShort { get { return LC.L(@"Public key username"); } }
public static string KeyUsernameLong { get { return LC.L(@"A username to append to the public key"); } }
public static string KeyTypeShort { get { return LC.L(@"The key type"); } }
public static string KeyTypeLong { get { return LC.L(@"Determines the type of key to generate"); } }
public static string KeyLenShort { get { return LC.L(@"The key length"); } }
public static string KeyLenLong { get { return LC.L(@"The length of the key in bits"); } }
}
internal static class KeyUploader {
public static string Description { get { return LC.L(@"Module for uploading SSH public keys"); } }
public static string DisplayName { get { return LC.L(@"SSH Key Uploader"); } }
public static string UrlShort { get { return LC.L(@"The SSH connection URL"); } }
public static string UrlLong { get { return LC.L(@"The SSH connection URL used to establish the connection"); } }
public static string PubkeyShort { get { return LC.L(@"The SSH public key to append"); } }
public static string PubkeyLong { get { return LC.L(@"The SSH public key must be a valid SSH string, which is appended to the .ssh/authorized_keys file"); } }
}
internal static class SSHv2Backend {
public static string Description { get { return LC.L(@"This backend can read and write data to an SSH based backend, using SFTP. Allowed formats are ""ssh://hostname/folder"" or ""ssh://username:password@hostname/folder""."); } }
public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } }
public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supplies the password used to connect to the server"); } }
public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } }
public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supplies the username used to connect to the server"); } }
public static string DescriptionFingerprintLong { get { return LC.L(@"The server fingerprint used for validation of server identity. Format is eg. ""ssh-rsa 4096 11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66""."); } }
public static string DescriptionFingerprintShort { get { return LC.L(@"Supplies server fingerprint used for validation of server identity"); } }
public static string DescriptionSshkeyfileLong { get { return LC.L(@"Points to a valid OpenSSH keyfile. If the file is encrypted, the password supplied is used to decrypt the keyfile. If this option is supplied, the password is not used to authenticate. This option only works when using the managed SSH client."); } }
public static string DescriptionSshkeyfileShort { get { return LC.L(@"Uses a SSH private key to authenticate"); } }
public static string DescriptionSshkeyLong(string urlprefix) { return LC.L(@"An url-encoded SSH private key. The private key must be prefixed with {0}. If the file is encrypted, the password supplied is used to decrypt the keyfile. If this option is supplied, the password is not used to authenticate. This option only works when using the managed SSH client.", urlprefix); }
public static string DescriptionSshkeyShort { get { return LC.L(@"Uses a SSH private key to authenticate"); } }
public static string DisplayName { get { return LC.L(@"SFTP (SSH)"); } }
public static string FolderNotFoundManagedError(string foldername, string message) { return LC.L(@"Unable to set folder to {0}, error message: {1}", foldername, message); }
public static string FingerprintNotMatchManagedError(string fingerprint) { return LC.L(@"Validation of server fingerprint failed. Server returned fingerprint ""{0}"". Cause of this message is either not correct configuration or Man-in-the-middle attack!", fingerprint); }
public static string FingerprintNotSpecifiedManagedError(string fingerprint, string hostkeyoption, string allkeysoptions) { return LC.L(@"Please add --{1}=""{0}"" to trust this host. Optionally you can use --{2} (NOT SECURE) for testing!", fingerprint, hostkeyoption, allkeysoptions); }
}
}
@@ -38,7 +38,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="SharpAESCrypt">
<HintPath>..\..\..\packages\SharpAESCrypt.exe.1.3\lib\net20\SharpAESCrypt.exe</HintPath>
<HintPath>..\..\..\packages\SharpAESCrypt.exe.1.3.2\lib\net45\SharpAESCrypt.exe</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SharpAESCrypt.exe" version="1.3" targetFramework="net45" />
<package id="SharpAESCrypt.exe" version="1.3.2" targetFramework="net45" />
</packages>
@@ -110,8 +110,12 @@ namespace Duplicati.Library.Main.Operation.Backup
/// <param name="attributes">The file or folder attributes.</param>
private static async Task<bool> AttributeFilterAsync(string rootpath, string path, FileAttributes attributes, Snapshots.ISnapshotService snapshot, LogWrapper log, Library.Utility.IFilter sourcefilter, Options.HardlinkStrategy hardlinkPolicy, Options.SymlinkStrategy symlinkPolicy, Dictionary<string, string> hardlinkmap, FileAttributes attributeFilter, Duplicati.Library.Utility.IFilter enumeratefilter, Queue<string> mixinqueue)
{
// Step 1, exclude block devices
try
// Report errors
if ((attributes & Library.Utility.Utility.ATTRIBUTE_ERROR) == Library.Utility.Utility.ATTRIBUTE_ERROR)
await log.WriteWarningAsync(string.Format("Error reported while accessing path {0}", path), null);
// Step 1, exclude block devices
try
{
if (snapshot.IsBlockDevice(path))
{
@@ -41,7 +41,6 @@ namespace Duplicati.Library.Main.Operation
throw new Exception(Strings.Common.PassphraseChangeUnsupported);
}
public static Snapshots.ISnapshotService GetSnapshot(string[] sources, Options options, ILogWriter log)
{
try
@@ -150,7 +150,7 @@ namespace Duplicati.Library.Main.Operation
public static void CreateVerificationFile(LocalDatabase db, System.IO.StreamWriter stream)
{
var s = new Newtonsoft.Json.JsonSerializer();
s.Serialize(stream, db.GetRemoteVolumes().Cast<IRemoteVolume>().ToArray());
s.Serialize(stream, db.GetRemoteVolumes().Where(x => x.State != RemoteVolumeState.Temporary).Cast<IRemoteVolume>().ToArray());
}
/// <summary>
@@ -1,47 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3526D1E7-64D1-4659-918C-5FC18FBD0905}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Duplicati.Library.SharpExpect</RootNamespace>
<AssemblyName>Duplicati.Library.SharpExpect</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SharpExpectProcess.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -1,54 +0,0 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Duplicati.Library.SharpExpect")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("GEOGRAF A/S")]
[assembly: AssemblyProduct("Duplicati.Library.SharpExpect")]
[assembly: AssemblyCopyright("Copyright © GEOGRAF A/S 2009")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("786d822a-f3fa-402a-a0dc-0e30f88937ea")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyFileVersion("2.0.0.7")]
@@ -1,152 +0,0 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.SharpExpect
{
public class SharpExpectProcess
{
private System.Diagnostics.Process m_process;
private object m_lock = new object();
private System.Threading.Thread m_stdOutReader;
private System.Threading.Thread m_stdErrReader;
private Queue<string> m_stdOut = new Queue<string>();
private Queue<string> m_stdErr = new Queue<string>();
private System.Threading.AutoResetEvent m_event = new System.Threading.AutoResetEvent(false);
public SharpExpectProcess(System.Diagnostics.Process process)
{
m_process = process;
if (m_process.StartInfo.RedirectStandardError)
{
m_stdErrReader = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(StreamReader));
m_stdErrReader.Start(m_process.StandardError);
}
if (m_process.StartInfo.RedirectStandardOutput)
{
m_stdOutReader = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(StreamReader));
m_stdOutReader.Start(m_process.StandardOutput);
}
}
public string GetNextOutputLine(int millisecondsTimeout)
{
//Is output buffered?
lock (m_lock)
{
if (m_stdOut.Count > 0)
return m_stdOut.Dequeue();
if (m_stdErr.Count > 0)
return m_stdErr.Dequeue();
}
if (millisecondsTimeout > 0)
{
m_event.WaitOne(millisecondsTimeout);
return GetNextOutputLine(0);
}
else
return null;
}
public int Expect(int millisecondsTimeout, params string[] possibilities)
{
List<KeyValuePair<System.Text.RegularExpressions.Regex, int>> lst = new List<KeyValuePair<System.Text.RegularExpressions.Regex, int>>();
for (int i = 0; i < possibilities.Length; i++)
if (!string.IsNullOrEmpty(possibilities[i]))
lst.Add(new KeyValuePair<System.Text.RegularExpressions.Regex, int>(new System.Text.RegularExpressions.Regex(possibilities[i]), i));
return Expect<int>(millisecondsTimeout, lst);
}
public T Expect<T>(int millisecondsTimeout, List<KeyValuePair<string, T>> possibilities)
{
List<KeyValuePair<System.Text.RegularExpressions.Regex, T>> lst = new List<KeyValuePair<System.Text.RegularExpressions.Regex, T>>();
for (int i = 0; i < possibilities.Count; i++)
if (!string.IsNullOrEmpty(possibilities[i].Key))
lst.Add(new KeyValuePair<System.Text.RegularExpressions.Regex, T>(new System.Text.RegularExpressions.Regex(possibilities[i].Key), possibilities[i].Value));
return Expect<T>(millisecondsTimeout, possibilities);
}
public T Expect<T>(int millisecondsTimeout, List<KeyValuePair<System.Text.RegularExpressions.Regex ,T>> possibilities)
{
if (possibilities == null || possibilities.Count == 0)
return default(T);
string line = GetNextOutputLine(millisecondsTimeout);
if (line == null)
return default(T);
foreach (KeyValuePair<System.Text.RegularExpressions.Regex, T> expr in possibilities)
if (expr.Key.Match(line).Success)
return expr.Value;
return default(T);
}
public void Sendline(string line)
{
m_process.StandardInput.WriteLine(line);
}
private void StreamReader(object input)
{
char[] buf = new char[1024];
System.IO.StreamReader sr = (System.IO.StreamReader)input;
Queue<string> queue = sr == m_process.StandardError ? m_stdErr : m_stdOut;
while (!m_process.HasExited)
{
int r = sr.Read(buf, 0, buf.Length);
if (r > 0)
{
lock (m_lock)
{
queue.Enqueue(new string(buf, 0, r));
m_event.Set();
}
}
}
}
public System.Diagnostics.Process Process { get { return m_process; } }
public static SharpExpectProcess Spawn(string FileName, string Arguments)
{
System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(FileName, Arguments);
pi.RedirectStandardError = true;
pi.RedirectStandardOutput = true;
pi.RedirectStandardInput = true;
return Spawn(pi);
}
public static SharpExpectProcess Spawn(System.Diagnostics.ProcessStartInfo startInfo)
{
return new SharpExpectProcess(System.Diagnostics.Process.Start(startInfo));
}
}
}
@@ -218,9 +218,10 @@ namespace Duplicati.Library.Snapshots
tmp = Alphaleonis.Win32.Filesystem.Directory.GetDirectories(spath);
}
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
for (int i = 0; i < tmp.Length; i++)
tmp[i] = root + tmp[i].Substring(volumePath.Length);
tmp[i] = root + SystemIOWindows.PrefixWithUNC(tmp[i]).Substring(volumePath.Length);
return tmp;
}
@@ -254,8 +255,10 @@ namespace Duplicati.Library.Snapshots
tmp = Alphaleonis.Win32.Filesystem.Directory.GetFiles(spath);
}
volumePath = SystemIOWindows.PrefixWithUNC(volumePath);
for (int i = 0; i < tmp.Length; i++)
tmp[i] = root + tmp[i].Substring(volumePath.Length);
tmp[i] = root + SystemIOWindows.PrefixWithUNC(tmp[i]).Substring(volumePath.Length);
return tmp;
}
@@ -390,7 +393,7 @@ namespace Duplicati.Library.Snapshots
/// <param name="file">The file or folder to examine</param>
public Dictionary<string, string> GetMetadata(string file)
{
return _ioWin.GetMetadata(file);
return _ioWin.GetMetadata(GetSnapshotPath(file));
}
/// <summary>
@@ -407,7 +410,7 @@ namespace Duplicati.Library.Snapshots
/// Gets a unique hardlink target ID
/// </summary>
/// <returns>The hardlink ID</returns>
/// <param name="file">The file or folder to examine</param>
/// <param name="path">The file or folder to examine</param>
public string HardlinkTargetID(string path)
{
return null;
@@ -67,6 +67,7 @@
<Compile Include="FilterCollector.cs" />
<Compile Include="HashCalculatingStream.cs" />
<Compile Include="DirectStreamLink.cs" />
<Compile Include="HostKeyException.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@@ -0,0 +1,65 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
namespace Duplicati.Library.Utility
{
/// <summary>
/// Class for reporting host-key violations
/// </summary>
[Serializable]
public class HostKeyException : Exception
{
/// <summary>
/// The key reported by the host, which is not accepted
/// </summary>
/// <value>The reported host key.</value>
public string ReportedHostKey { get; set; }
/// <summary>
/// The key which was expected
/// </summary>
/// <value>The acceptable host key.</value>
public string AcceptedHostKey { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Utility.HostKeyException"/> class.
/// </summary>
/// <param name="message">The error message to report.</param>
/// <param name="reportedkey">The reported host key.</param>
/// <param name="acceptedkey">The acceptable host key.</param>
/// <param name="innerexception">The inner exception.</param>
public HostKeyException(string message, string reportedkey, string acceptedkey, Exception innerexception)
: base(message, innerexception)
{
ReportedHostKey = reportedkey;
AcceptedHostKey = acceptedkey;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Utility.HostKeyException"/> class.
/// </summary>
/// <param name="message">The error message to report.</param>
/// <param name="reportedkey">The reported host key.</param>
/// <param name="acceptedkey">The acceptable host key.</param>
public HostKeyException(string message, string reportedkey, string acceptedkey)
: base(message)
{
ReportedHostKey = reportedkey;
AcceptedHostKey = acceptedkey;
}
}
}
+7 -2
View File
@@ -32,7 +32,7 @@ namespace Duplicati.Library.Utility
size = size.ToLower().Trim();
if (size.EndsWith("gb") || size.EndsWith("mb") || size.EndsWith("kb") || size.EndsWith("b"))
if (size.EndsWith("tb") || size.EndsWith("gb") || size.EndsWith("mb") || size.EndsWith("kb") || size.EndsWith("b"))
return ParseSize(size);
else
return ParseSize(size + " " + defaultSuffix);
@@ -49,7 +49,12 @@ namespace Duplicati.Library.Utility
long factor = 1;
if (size.EndsWith("gb"))
if (size.EndsWith("tb"))
{
factor = 1024L * 1024 * 1024 * 1024;
size = size.Substring(0, size.Length - 2).Trim();
}
else if (size.EndsWith("gb"))
{
factor = 1024 * 1024 * 1024;
size = size.Substring(0, size.Length - 2).Trim();
+1 -1
View File
@@ -260,7 +260,7 @@ namespace Duplicati.Library.Utility
if (!string.IsNullOrEmpty(path))
{
if (!string.IsNullOrEmpty(host))
if (!string.IsNullOrEmpty(host) && !path.StartsWith("/"))
s += "/";
s += path;
}
@@ -46,6 +46,7 @@ namespace Duplicati.Server.Serialization
Repair,
RepairUpdate,
Verify,
Compact,
CreateReport
}
+4
View File
@@ -284,6 +284,10 @@
<Project>{BB014EA5-CE2C-4444-8D30-38983A0E8553}</Project>
<Name>Duplicati.Library.UsageReporter</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="webroot\" />
+3 -1
View File
@@ -621,7 +621,9 @@ namespace Duplicati.Server
return Strings.TaskType.ListActualFiles;
case Duplicati.Server.Serialization.DuplicatiOperation.Verify:
return Strings.TaskType.ListBackupEntries;
case Duplicati.Server.Serialization.DuplicatiOperation.Restore:
case Duplicati.Server.Serialization.DuplicatiOperation.Compact:
return Strings.TaskType.CompactRemoteFiles;
case Duplicati.Server.Serialization.DuplicatiOperation.Restore:
return Strings.TaskType.ListBackups;
default:
return type.ToString();
+8 -1
View File
@@ -464,7 +464,14 @@ namespace Duplicati.Server
return r;
}
case DuplicatiOperation.CreateReport:
case DuplicatiOperation.Compact:
{
var r = controller.Compact();
UpdateMetadata(backup, r);
return r;
}
case DuplicatiOperation.CreateReport:
{
using(var tf = new Duplicati.Library.Utility.TempFile())
{
+2 -1
View File
@@ -38,7 +38,8 @@ Error message: {0}", error); }
public static string ListActualFiles { get { return LC.L(@"List actual files"); } }
public static string ListBackupEntries { get { return LC.L(@"List backup entries"); } }
public static string ListBackups { get { return LC.L(@"List backups"); } }
}
public static string CompactRemoteFiles { get { return LC.L(@"Compact remote files"); } }
}
internal static class Scheduler {
public static string InvalidTimeSetupError(System.DateTime startdate, string interval, string alloweddays) { return LC.L(@"Unable to find a valid date, given the start date {0}, the repetition interval {1} and the allowed days {2}", startdate, interval, alloweddays); }
}
@@ -215,7 +215,16 @@ namespace Duplicati.Server.WebServer.RESTMethods
info.OutputOK(new {Status = "OK", ID = task.TaskID});
}
private void DoRepair(IBackup backup, RequestInfo info, bool repairUpdate)
private void Compact(IBackup backup, RequestInfo info)
{
var task = Runner.CreateTask(DuplicatiOperation.Compact, backup);
Program.WorkThread.AddTask(task);
Program.StatusEventNotifyer.SignalNewEvent();
info.OutputOK(new { Status = "OK", ID = task.TaskID });
}
private void DoRepair(IBackup backup, RequestInfo info, bool repairUpdate)
{
var input = info.Request.Form;
string[] filters = null;
@@ -415,7 +424,11 @@ namespace Duplicati.Server.WebServer.RESTMethods
Verify(bk, info);
return;
case "start":
case "compact":
Compact(bk, info);
return;
case "start":
case "run":
RunBackup(bk, info);
return;
@@ -70,40 +70,53 @@ namespace Duplicati.Server.WebServer.RESTMethods
where n is Library.Interface.IConnectionModule
select n).ToArray();
try
{
var uri = new Library.Utility.Uri(url);
var qp = uri.QueryParameters;
try
{
var uri = new Library.Utility.Uri(url);
var qp = uri.QueryParameters;
var opts = new Dictionary<string, string>();
foreach(var k in qp.Keys.Cast<string>())
opts[k] = qp[k];
var opts = new Dictionary<string, string>();
foreach (var k in qp.Keys.Cast<string>())
opts[k] = qp[k];
foreach(var n in modules)
n.Configure(opts);
using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(url, new Dictionary<string, string>()))
b.Test();
info.OutputOK();
}
catch (Duplicati.Library.Interface.FolderMissingException)
{
info.ReportServerError("missing-folder");
}
catch (Duplicati.Library.Utility.SslCertificateValidator.InvalidCertificateException icex)
{
if (string.IsNullOrWhiteSpace(icex.Certificate))
info.ReportServerError(icex.Message);
else
info.ReportServerError("incorrect-cert:" + icex.Certificate);
}
finally
{
foreach(var n in modules)
if (n is IDisposable)
((IDisposable)n).Dispose();
}
foreach (var n in modules)
n.Configure(opts);
using (var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(url, new Dictionary<string, string>()))
b.Test();
info.OutputOK();
}
catch (Duplicati.Library.Interface.FolderMissingException)
{
info.ReportServerError("missing-folder");
}
catch (Duplicati.Library.Utility.SslCertificateValidator.InvalidCertificateException icex)
{
if (string.IsNullOrWhiteSpace(icex.Certificate))
info.ReportServerError(icex.Message);
else
info.ReportServerError("incorrect-cert:" + icex.Certificate);
}
catch (Duplicati.Library.Utility.HostKeyException hex)
{
if (string.IsNullOrWhiteSpace(hex.ReportedHostKey))
info.ReportServerError(hex.Message);
else
{
info.ReportServerError(string.Format(
@"incorrect-host-key:""{0}"", accepted-host-key:""{1}""",
hex.ReportedHostKey,
hex.AcceptedHostKey
));
}
}
finally
{
foreach (var n in modules)
if (n is IDisposable)
((IDisposable)n).Dispose();
}
}
public void GET(string key, RequestInfo info)
@@ -17,6 +17,10 @@ backupApp.controller('HomeController', function ($scope, $location, BackupList,
$location.path('/export/' + id);
};
$scope.doCompact = function(id) {
AppService.post('/backup/' + id + '/compact');
};
$scope.doDelete = function(id, name) {
DialogService.dialog('Confirm delete', 'Do you really want to delete the backup: ' + name, ['No', 'Yes'], function(ix) {
if (ix == 1)
@@ -2,298 +2,347 @@ backupApp.directive('backupEditUri', function() {
return {
restrict: 'E',
scope: {
uri: '=uri',
hide: '=hide'
uri: '=uri',
hide: '=hide'
},
templateUrl: 'templates/edituri.html',
controller: function($scope, AppService, AppUtils, SystemInfo, EditUriBackendConfig, DialogService, EditUriBuiltins) {
var scope = $scope;
var scope = $scope;
var builduri = function(callback) {
var builduri = function(callback) {
function validationCompleted() {
if (EditUriBackendConfig.builders[scope.Backend.Key] == null)
callback(EditUriBackendConfig.defaultbuilder(scope));
else
callback(EditUriBackendConfig.builders[scope.Backend.Key](scope));
};
function validationCompleted() {
if (EditUriBackendConfig.builders[scope.Backend.Key] == null)
callback(EditUriBackendConfig.defaultbuilder(scope));
else
callback(EditUriBackendConfig.builders[scope.Backend.Key](scope));
};
if (EditUriBackendConfig.validaters[scope.Backend.Key] == null)
EditUriBackendConfig.defaultvalidater(scope, validationCompleted);
else
EditUriBackendConfig.validaters[scope.Backend.Key](scope, validationCompleted);
}
if (EditUriBackendConfig.validaters[scope.Backend.Key] == null)
EditUriBackendConfig.defaultvalidater(scope, validationCompleted);
else
EditUriBackendConfig.validaters[scope.Backend.Key](scope, validationCompleted);
}
function performConnectionTest(uri) {
function performConnectionTest(uri) {
var hasTriedCreate = false;
var hasTriedCert = false;
var hasTriedMozroots = false;
var dlg = null;
var hasTriedCreate = false;
var hasTriedCert = false;
var hasTriedMozroots = false;
var hasTriedHostkey = false;
var dlg = null;
var testConnection = function() {
scope.Testing = true;
if (dlg != null)
dlg.dismiss();
var testConnection = function() {
scope.Testing = true;
if (dlg != null)
dlg.dismiss();
dlg = DialogService.dialog('Testing ...', 'Testing connection ...', [], null, function() {
AppService.post('/remoteoperation/test', uri).then(function() {
scope.Testing = false;
dlg.dismiss();
dlg = DialogService.dialog('Testing ...', 'Testing connection ...', [], null, function() {
AppService.post('/remoteoperation/test', uri).then(function() {
scope.Testing = false;
dlg.dismiss();
if (EditUriBackendConfig.testers[scope.Backend.Key] != null)
EditUriBackendConfig.testers[scope.Backend.Key](scope, function() {
DialogService.dialog('Success', 'Connection worked!');
});
else
DialogService.dialog('Success', 'Connection worked!');
if (EditUriBackendConfig.testers[scope.Backend.Key] != null)
EditUriBackendConfig.testers[scope.Backend.Key](scope, function() {
DialogService.dialog('Success', 'Connection worked!');
});
else
DialogService.dialog('Success', 'Connection worked!');
}, handleError);
});
}, handleError);
});
};
};
var createFolder = function() {
scope.Testing = true;
AppService.post('/remoteoperation/create', uri).then(testConnection, handleError);
};
var createFolder = function() {
hasTriedCreate = true;
scope.Testing = true;
AppService.post('/remoteoperation/create', uri).then(testConnection, handleError);
};
var appendApprovedCert = function(hash)
{
for(var n in scope.AdvancedOptions) {
if (scope.AdvancedOptions[n].indexOf('--accept-specified-ssl-hash=') == 0)
{
var certs = scope.AdvancedOptions[n].substr('--accept-specified-ssl-hash='.length).split(',');
for(var i in certs)
if (certs[i] == hash)
return;
var appendApprovedCert = function(hash)
{
hasTriedCert = true;
for(var n in scope.AdvancedOptions) {
if (scope.AdvancedOptions[n].indexOf('--accept-specified-ssl-hash=') == 0)
{
var certs = scope.AdvancedOptions[n].substr('--accept-specified-ssl-hash='.length).split(',');
for(var i in certs)
if (certs[i] == hash)
return;
scope.AdvancedOptions[n] += ',' + hash;
return;
}
}
scope.AdvancedOptions[n] += ',' + hash;
return;
}
}
scope.AdvancedOptions.push('--accept-specified-ssl-hash=' + hash);
}
scope.AdvancedOptions.push('--accept-specified-ssl-hash=' + hash);
}
var askApproveCert = function(hash) {
DialogService.dialog('Trust server certificate?', 'The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: ' + hash + '?', ['No', 'Yes'], function(ix) {
if (ix == 1) {
appendApprovedCert(hash);
builduri(function(res) {
uri = res;
testConnection();
});
}
});
};
var hasCertApproved = function(hash)
{
for(var n in scope.AdvancedOptions) {
if (scope.AdvancedOptions[n].indexOf('--accept-specified-ssl-hash=') == 0)
{
var certs = scope.AdvancedOptions[n].substr('--accept-specified-ssl-hash='.length).split(',');
for(var i in certs)
if (certs[i] == hash)
return true;
break;
}
}
return false;
}
var handleError = function(data) {
scope.Testing = false;
if (dlg != null)
dlg.dismiss();
var message = data.statusText;
if (!hasTriedCreate && message == 'missing-folder')
{
var folder = scope.Folder;
if ((folder || "") == "")
folder = scope.Path;
if ((folder || "") == "")
folder = '';
DialogService.dialog('Create folder?', 'The folder ' + folder + ' does not exist\nCreate it now?', ['No', 'Yes'], function(ix) {
if (ix == 1)
createFolder();
});
}
else if (!hasTriedCert && message.indexOf('incorrect-cert:') == 0)
{
var hash = message.substr('incorrect-cert:'.length);
if (hasCertApproved(hash)) {
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to connect: ' + message);
return;
}
if ($scope.SystemInfo.MonoVersion != null && !hasTriedMozroots) {
hasTriedMozroots = true;
AppService.post('/webmodule/check-mono-ssl', {'mono-ssl-config': 'List'}).then(function(data) {
if (data.data.Result.count == 0) {
if (confirm('You appear to be running Mono with no SSL certificates loaded.\nDo you want to import the list of trusted certificates from Mozilla?'))
{
scope.Testing = true;
AppService.post('/webmodule/check-mono-ssl', {'mono-ssl-config': 'Install'}).then(function(data) {
scope.Testing = false;
if (data.data.Result.count == 0) {
DialogService.dialog('Import failed', 'Import completed, but no certificates were found after the import');
} else {
testConnection();
}
}, function(resp) {
scope.Testing = false;
message = resp.statusText;
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to import: ' + message);
});
}
else
{
askApproveCert(hash);
}
} else {
askApproveCert(hash);
}
}, AppUtils.connectionError);
}
else
{
askApproveCert(hash);
}
}
else if (!hasTriedHostkey && message.indexOf('incorrect-host-key:') == 0)
{
var re = /incorrect-host-key\s*:\s*"([^"]*)"(,\s*accepted-host-key\s*:\s*"([^"]*)")?/;
var m = re.exec(message);
var key = null;
var prev = null;
if (m != null) {
key = m[1] || '';
prev = m[3] || '';
}
if ((key || '').trim().length == 0 || key == prev) {
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to connect: ' + message);
}
else
{
var message = ((prev || '').trim().length == 0) ?
('No certificate was specified previously, please verify with the server administrator that the key is correct: ' + key + ' \n\nDo you want to approve the reported host key?')
:
('The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key "' + prev + '" with the REPORTED host key: ' + key + '?');
DialogService.dialog('Trust host certificate?', message, ['No', 'Yes'], function(ix) {
if (ix == 1) {
hasTriedHostkey = true;
for(var n in scope.AdvancedOptions) {
if (scope.AdvancedOptions[n].indexOf('--ssh-fingerprint=') == 0) {
scope.AdvancedOptions.splice(n, 1);
break;
}
}
scope.AdvancedOptions.push('--ssh-fingerprint=' + key);
builduri(function(res) {
uri = res;
testConnection();
});
}
});
}
}
else
{
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to connect: ' + message);
}
}
testConnection();
};
$scope.testConnection = function() {
builduri(performConnectionTest);
};
$scope.save = function() {
builduri(function(res) {
scope.uri = res;
scope.hide();
});
};
$scope.contains_value = AppUtils.contains_value;
$scope.$watch('Backend', function() {
if (scope.Backend == null) {
$scope.TemplateUrl = null;
$scope.AdvanceOptionList = null;
return;
}
var opts = angular.copy(scope.Backend.Options);
for(var n in opts)
opts[n].Category = scope.Backend.DisplayName;
for(var m in SystemInfo.state.ConnectionModules)
{
var t = angular.copy(SystemInfo.state.ConnectionModules[m].Options);
for(var n in t)
t[n].Category = SystemInfo.state.ConnectionModules[m].DisplayName;
opts.push.apply(opts, t);
}
$scope.AdvanceOptionList = opts;
scope.SupportsSSL = false;
for(var n in scope.Backend.Options)
if (scope.Backend.Options[n].Name == 'use-ssl')
scope.SupportsSSL = true;
scope.TemplateUrl = EditUriBackendConfig.templates[scope.Backend.Key];
if (scope.TemplateUrl == null)
scope.TemplateUrl = EditUriBackendConfig.defaulttemplate;
if (EditUriBackendConfig.loaders[scope.Backend.Key] == null)
return;
EditUriBackendConfig.loaders[scope.Backend.Key](scope);
});
var reparseuri = function() {
scope.Backend = scope.DefaultBackend;
var parts = AppUtils.decode_uri(scope.uri);
for(var n in scope.SystemInfo.GroupedBackendModules) {
if (scope.SystemInfo.GroupedBackendModules[n].Key == parts['backend-type']) {
scope.Backend = $scope.SystemInfo.GroupedBackendModules[n];
break;
}
if ((scope.SystemInfo.GroupedBackendModules[n].Key + 's') == parts['backend-type']) {
var hasssl = false;
var bk = scope.SystemInfo.GroupedBackendModules[n];
for(var o in bk.Options) {
if (bk.Options[o].Name == 'use-ssl') {
hasssl = true;
break;
}
}
if (hasssl) {
scope.Backend = bk;
parts['--use-ssl'] = true;
break;
}
}
}
var askApproveCert = function(hash) {
DialogService.dialog('Trust server certificate?', 'The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: ' + hash + '?', ['No', 'Yes'], function(ix) {
if (ix == 1) {
appendApprovedCert(hash);
testConnection();
}
});
};
scope.Username = parts['--auth-username'];
scope.Password = parts['--auth-password'];
scope.UseSSL = parts['--use-ssl'];
scope.Port = parts['server-port'];
scope.Server = parts['server-name'];
scope.Path = parts['server-path'];
var hasCertApproved = function(hash)
{
for(var n in scope.AdvancedOptions) {
if (scope.AdvancedOptions[n].indexOf('--accept-specified-ssl-hash=') == 0)
{
var certs = scope.AdvancedOptions[n].substr('--accept-specified-ssl-hash='.length).split(',');
for(var i in certs)
if (certs[i] == hash)
return true;
if (scope.Backend != null && scope.uri != null && EditUriBackendConfig.parsers[scope.Backend.Key])
EditUriBackendConfig.parsers[scope.Backend.Key](scope, parts['backend-type'], parts['server-name'], parts['server-path'], parts['server-port'], parts);
break;
}
}
delete parts['--auth-username'];
delete parts['--auth-password'];
delete parts['--use-ssl'];
scope.AdvancedOptions = AppUtils.serializeAdvancedOptionsToArray(parts);
};
return false;
}
$scope.SystemInfo = SystemInfo.watch($scope, function() {
for(var n in scope.SystemInfo.GroupedBackendModules)
if (scope.SystemInfo.GroupedBackendModules[n].Key == EditUriBackendConfig.defaultbackend)
scope.DefaultBackend = scope.SystemInfo.GroupedBackendModules[n];
var handleError = function(data) {
scope.Testing = false;
if (dlg != null)
dlg.dismiss();
var message = data.statusText;
if (!hasTriedCreate && message == 'missing-folder')
{
var folder = scope.Folder;
if ((folder || "") == "")
folder = scope.Path;
if ((folder || "") == "")
folder = '';
DialogService.dialog('Create folder?', 'The folder ' + folder + ' does not exist\nCreate it now?', ['No', 'Yes'], function(ix) {
if (ix == 1)
createFolder();
});
}
else if (!hasTriedCert && message.indexOf('incorrect-cert:') == 0)
{
var hash = message.substr('incorrect-cert:'.length);
if (hasCertApproved(hash)) {
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to connect: ' + message);
return;
}
if ($scope.SystemInfo.MonoVersion != null && !hasTriedMozroots) {
hasTriedMozroots = true;
AppService.post('/webmodule/check-mono-ssl', {'mono-ssl-config': 'List'}).then(function(data) {
if (data.data.Result.count == 0) {
if (confirm('You appear to be running Mono with no SSL certificates loaded.\nDo you want to import the list of trusted certificates from Mozilla?'))
{
scope.Testing = true;
AppService.post('/webmodule/check-mono-ssl', {'mono-ssl-config': 'Install'}).then(function(data) {
scope.Testing = false;
if (data.data.Result.count == 0) {
DialogService.dialog('Import failed', 'Import completed, but no certificates were found after the import');
} else {
testConnection();
}
}, function(resp) {
scope.Testing = false;
message = resp.statusText;
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to import: ' + message);
});
}
else
{
askApproveCert(hash);
}
} else {
askApproveCert(hash);
}
}, AppUtils.connectionError);
}
else
{
askApproveCert(hash);
}
}
else
{
if (data.data != null && data.data.Message != null)
message = data.data.Message;
DialogService.dialog('Error', 'Failed to connect: ' + message);
}
}
testConnection();
};
$scope.testConnection = function() {
builduri(performConnectionTest);
};
$scope.save = function() {
builduri(function(res) {
scope.uri = res;
scope.hide();
});
};
$scope.contains_value = AppUtils.contains_value;
$scope.$watch('Backend', function() {
if (scope.Backend == null) {
$scope.TemplateUrl = null;
$scope.AdvanceOptionList = null;
return;
}
var opts = angular.copy(scope.Backend.Options);
for(var n in opts)
opts[n].Category = scope.Backend.DisplayName;
for(var m in SystemInfo.state.ConnectionModules)
{
var t = angular.copy(SystemInfo.state.ConnectionModules[m].Options);
for(var n in t)
t[n].Category = SystemInfo.state.ConnectionModules[m].DisplayName;
opts.push.apply(opts, t);
}
$scope.AdvanceOptionList = opts;
scope.SupportsSSL = false;
for(var n in scope.Backend.Options)
if (scope.Backend.Options[n].Name == 'use-ssl')
scope.SupportsSSL = true;
scope.TemplateUrl = EditUriBackendConfig.templates[scope.Backend.Key];
if (scope.TemplateUrl == null)
scope.TemplateUrl = EditUriBackendConfig.defaulttemplate;
if (EditUriBackendConfig.loaders[scope.Backend.Key] == null)
return;
EditUriBackendConfig.loaders[scope.Backend.Key](scope);
});
var reparseuri = function() {
scope.Backend = scope.DefaultBackend;
var parts = AppUtils.decode_uri(scope.uri);
for(var n in scope.SystemInfo.GroupedBackendModules) {
if (scope.SystemInfo.GroupedBackendModules[n].Key == parts['backend-type']) {
scope.Backend = $scope.SystemInfo.GroupedBackendModules[n];
break;
}
if ((scope.SystemInfo.GroupedBackendModules[n].Key + 's') == parts['backend-type']) {
var hasssl = false;
var bk = scope.SystemInfo.GroupedBackendModules[n];
for(var o in bk.Options) {
if (bk.Options[o].Name == 'use-ssl') {
hasssl = true;
break;
}
}
if (hasssl) {
scope.Backend = bk;
parts['--use-ssl'] = true;
break;
}
}
}
scope.Username = parts['--auth-username'];
scope.Password = parts['--auth-password'];
scope.UseSSL = parts['--use-ssl'];
scope.Port = parts['server-port'];
scope.Server = parts['server-name'];
scope.Path = parts['server-path'];
if (scope.Backend != null && scope.uri != null && EditUriBackendConfig.parsers[scope.Backend.Key])
EditUriBackendConfig.parsers[scope.Backend.Key](scope, parts['backend-type'], parts['server-name'], parts['server-path'], parts['server-port'], parts);
delete parts['--auth-username'];
delete parts['--auth-password'];
delete parts['--use-ssl'];
scope.AdvancedOptions = AppUtils.serializeAdvancedOptionsToArray(parts);
};
$scope.SystemInfo = SystemInfo.watch($scope, function() {
for(var n in scope.SystemInfo.GroupedBackendModules)
if (scope.SystemInfo.GroupedBackendModules[n].Key == EditUriBackendConfig.defaultbackend)
scope.DefaultBackend = scope.SystemInfo.GroupedBackendModules[n];
reparseuri();
});
$scope.$watch('uri', reparseuri);
}
reparseuri();
});
$scope.$watch('uri', reparseuri);
}
};
});
@@ -689,4 +689,4 @@ backupApp.service('EditUriBuiltins', function(AppService, AppUtils, SystemInfo,
};
});
});
@@ -218,12 +218,9 @@
<form class="styled">
<h2>General options</h2>
<div class="input multiple text select maxSize">
<label for="dblockSizeNumber">Max backup file size</label>
<label for="dblockSizeNumber">Upload volume size</label>
<input type="number" class="number" parse-size-number="uppercase" id="dblockSizeNumber" ng-model="Options['dblock-size']" />
<select parse-size-multiplier="uppercase" id="dblockSizeMultiplier" ng-model="Options['dblock-size']" ng-options="item.value as item.name for item in AppUtils.fileSizeMultipliers"></select>
<option value="MB">MByte</option>
<option value="GB">GByte</option>
<option value="TB">TByte</option>
<select parse-size-multiplier="uppercase" id="dblockSizeMultiplier" ng-model="Options['dblock-size']" ng-options="item.value as item.name for item in AppUtils.fileSizeMultipliers">
</select>
</div>
<div class="input multiple text select keepBackups">
@@ -268,4 +265,4 @@
</div>
<!-- .steps-boxes -->
</div>
<!-- .add -->
<!-- .add -->
@@ -1,6 +1,6 @@
<div ng-show="SupportsSSL" class="input checkbox multple">
<input type="checkbox" id="s3_ssl" ng-model="$parent.UseSSL" />
<div ng-show="SupportsSSL" class="input checkbox">
<label for="generic_ssl">Use SSL</label>
<input type="checkbox" style="width: 15px; margin-right: 65%;" name="generic_ssl" id="generic_ssl" ng-model="$parent.UseSSL" />
</div >
<div class="input text">
@@ -1,6 +1,6 @@
<div class="input checkbox multple">
<input type="checkbox" id="s3_ssl" ng-model="$parent.UseSSL" />
<div ng-show="SupportsSSL" class="input checkbox">
<label for="s3_ssl">Use SSL</label>
<input type="checkbox" style="width: 15px; margin-right: 65%;" name="s3_ssl" id="s3_ssl" ng-model="$parent.UseSSL" />
</div >
<div class="input select">
@@ -56,8 +56,9 @@
<dt>Advanced:</dt>
<dd>
<p ng-click="doLocalDb(item.Backup.ID)">Manage local database ...</p>
<p ng-click="doVerifyRemote(item.Backup.ID)">Verify backup files</p>
<p ng-click="doLocalDb(item.Backup.ID)">Manage database ...</p>
<p ng-click="doVerifyRemote(item.Backup.ID)">Verify files</p>
<p ng-click="doCompact(item.Backup.ID)">Compact now</p>
</dd>
<dt>Reporting:</dt>
+32
View File
@@ -0,0 +1,32 @@
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers to get all the necessary data to fix your issue.
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
Let's begin with a checklist: replace the empty checkbox [ ] below with a checked one [x] if you already searched for duplicate bugs -->
I have:
- [ ] searched open and closed issues for duplicates
----------------------------------------
### Version info
<!-- please replace the examples with your info -->
**Duplicati Version:** 0.0.0.0
**Operating System:**
**Backend:**
### Bug description
Describe here the issue that you are experiencing.
### Steps to reproduce
- using hyphens as bullet points
- list the steps
- that reproduce the bug
**Actual result:** Describe here what happens after you run the steps above (i.e. the buggy behaviour)
**Expected result:** Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour)
### Screenshots
<!-- you can drag and drop images below -->
### debug log
<!-- posting a debug log helps the developers to fix your issue -->
+27 -13
View File
@@ -1,18 +1,32 @@
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<runtime>
<loadFromRemoteSources enabled="true"/>
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Utility" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Interface" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
<!-- Support for loading older backends with the current executable version -->
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Utility" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<!-- Support for loading older backends with the current executable version -->
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Interface" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
<!-- Support for backends that wants older versions of NewtonSoft.Json -->
<!-- The spaces near the equation sign are used to prevent the version being set to the current build by the VersionStamp tool -->
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0 - 8.0.0.0" newVersion = "9.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
+94
View File
@@ -0,0 +1,94 @@
Set-StrictMode -Version 5
function HexToBase64
{
param
(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[string]$s
)
[array]$return = @()
for ($i = 0; $i -lt $s.Length ; $i += 2) {
$return += [Byte]::Parse($s.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
}
Write-Output $([Convert]::ToBase64String($return))
}
function Verify-Hashes
{
[CmdletBinding(PositionalBinding=$false)]
param
(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$filename
)
[int]$errorCount = 0
[int]$checked = 0
if(-not $(Test-Path $filename)) {
Write-Host "Specified file does not exist: $filename"
return -1
}
[string]$folder = Split-Path -Path $filename
$remoteVolumes = (Get-Content $filename) -Join "`n" | ConvertFrom-Json
foreach ($remoteVolume in $remoteVolumes) {
[string]$volFileName = $remoteVolume.Name
[long]$volFileSize = $remoteVolume.Size
[string]$volFilePath = Join-Path -Path $folder -ChildPath $volFileName
if(-not $(Test-Path $volFilePath)) {
Write-Host "File missing: $volFilePath" -ForegroundColor Red
$errorCount++
} else {
$checked++
Write-Host "Verifying file $volFileName"
$shaHash = Get-FileHash -Path $volFilePath -Algorithm SHA256
if($remoteVolume.Hash -ne $(HexToBase64 $shaHash.Hash)) {
Write-Host "*** Hash check failed for file: $volFileName" -ForegroundColor Red
$errorCount++
}
}
}
if($errorCount -gt 0) {
Write-Host "Errors were found" -ForegroundColor Red
} else {
Write-Host "No errors found" -ForegroundColor Green
}
return $errorCount
}
[string]$argument = ""
if ($args.Length -eq 1) { $argument = $args[0] }
else { $argument = $PSScriptRoot }
if(-not $(Test-Path $argument)) {
Write-Host "No such file or directory: $argument" -ForegroundColor Red
}
if(Test-Path $argument -PathType Leaf) {
Verify-Hashes -filename $argument
} else {
[int] $files = 0
Get-ChildItem $argument -Filter "*-verification.json" |
Foreach-Object {
$files++
Write-Host "Verifying file: $_.Name"
Verify-Hashes -filename $_.FullName
}
if ($files -eq 0) { Write-Host "No verification files in folder: $argument" }
}
+1 -1
View File
@@ -1 +1 @@
17
19
Regular → Executable
+3 -1
View File
@@ -137,7 +137,7 @@ if [ -f "${GPG_KEYFILE}" ]; then
echo
fi
GPGDATA=`mono thirdparty/SharpAESCrypt/SharpAESCrypt.exe d "${KEYFILE_PASSWORD}" "${GPG_KEYFILE}"`
GPGDATA=`mono BuildTools/AutoUpdateBuilder/bin/Debug/SharpAESCrypt.exe d "${KEYFILE_PASSWORD}" "${GPG_KEYFILE}"`
if [ ! $? -eq 0 ]; then
echo "Decrypting GPG keyfile failed"
exit 1
@@ -159,6 +159,8 @@ aws ec2 start-instances --profile=duplicati-builder --instance-ids "${WINDOWS_IN
echo ""
echo ""
echo "Building OSX package locally ..."
echo ""
echo "Enter local sudo password..."
cd Installer/OSX
bash "make-dmg.sh" "../../$1"
+12
View File
@@ -98,6 +98,8 @@ ${NUGET} restore Duplicati.sln
${XBUILD} /p:Configuration=Debug "BuildTools/AutoUpdateBuilder/AutoUpdateBuilder.sln"
${XBUILD} /p:Configuration=Release /target:Clean Duplicati.sln
find Duplicati -type d -name Release | xargs rm -rf
${XBUILD} /p:Configuration=Release Duplicati.sln
BUILD_STATUS=$?
@@ -120,6 +122,16 @@ mkdir "${UPDATE_TARGET}"
cp -R Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/* "${UPDATE_SOURCE}"
cp -R Duplicati/Server/webroot "${UPDATE_SOURCE}"
# We copy some files for alphavss manually as they are not picked up by xbuild
mkdir "${UPDATE_SOURCE}/alphavss"
for FN in "Duplicati/Library/Snapshots/bin/Release/SnapshotQuery.exe" "Duplicati/Library/Snapshots/bin/Release/AlphaShadow.exe" Duplicati/Library/Snapshots/bin/Release/AlphaVSS.*.*.dll; do
cp "${FN}" "${UPDATE_SOURCE}/alphavss/"
done
# Install the assembly redirects for all Duplicati .exe files
find "${UPDATE_SOURCE}" -type f -name Duplicati.*.exe -maxdepth 1 -exec cp Installer/AssemblyRedirects.xml {}.config \;
# Clean some unwanted build files
if [ -e "${UPDATE_SOURCE}/control_dir" ]; then rm -rf "${UPDATE_SOURCE}/control_dir"; fi
if [ -e "${UPDATE_SOURCE}/Duplicati-server.sqlite" ]; then rm "${UPDATE_SOURCE}/Duplicati-server.sqlite"; fi
if [ -e "${UPDATE_SOURCE}/Duplicati.debug.log" ]; then rm "${UPDATE_SOURCE}/Duplicati.debug.log"; fi
+32
View File
@@ -1,3 +1,35 @@
2016-08-27
==========
* Fixed issue with files not being listed on Windows (AlphaFS and AlphaVSS issue)
* Updated B2 API URL
* Added compact command to WebUI
2016-08-10
==========
* OneDrive for Business now uploads files in chunks to prevent timeout, thanks @ReneHezer
* Prevent using both --keep-version and --keep-time to avoid confusion
* Fixed a number of usability issues with the WebUI
* Fixed encoding of special characters for Amazon Cloud Drive, thanks @Xelio
* Fixed an issue with the recovery tool not respecting the restore folder target, thanks @mnaiman
* Fixed issues when including the "home" folder on Windows
* Fixed expanding environment variables and special folder names for filters
* Changed displaying the dates in the WebUI with the browser locale
* Added option to re-compress and re-encrypt from the recovery tool, thanks @mnaiman
* Updated AWS S3 library to latests version
* Added option to create an un-priviledged S3 user via IAM, if the current user has permissions
* Fixed an issue with the scheduler that cause high CPU usage for invalid schedules
* Fixed an issue with the WebUI forgetting SSL settings in some cases
* Upgraded support for sending email to use a new library with proper SSL support, thanks @mnaiman
* Fixed spurious failures in SSH connections, caused by leaking open connections, thanks @mnaiman
* Fixed a Mono dependency in the Debian package that has been renamed
* Added a DropBox backend, thanks @wvc
* Added option to serve the WebUI with an SSL certificate, thanks @mnaiman
* Improments to the recovery tool, thanks @mnaiman
* Fixed an issue where deleting a backup and creating a new could cause data to be pre-set from the delete backup on the new, thanks @4m1g0
* Fixed an issue with LVM not working on newer versions of Ubuntu
* Fixed an issue where Mono could not be located by the OSX app bundle
* Fixes to WebUI layout, thansk @Vaporizer8
2016-05-14
==========
* Fixes for UI on Safari, thanks @alexfran