Files
duplicati/Duplicati/GUI/ApplicationSetup.cs
T
kenneth.skovhede@gmail.com effdba0558 Update issue #134
Status: Fixed

I have now implemented a password protection control.
You can now only view passwords when you enter them.
After this, you may reset the passwords, by entering a
new one, but you cannot read the existing passwords.

To protect against people reading the database,
which contains the passwords in clear text, the
database is now encrypted by using the standard 
SQLite encryption method. This is different
than what I proposed, but is a much stronger
protection than merely scrambling the passwords,
and also protects all information (servername, etc).

It works by setting an environment variable
called DUPLICATI_DB_KEY. When no password
is set, Duplicati will use the key "Duplicati_Key_42",
ensuring that all databases are encrypted by default.

Using a pre-defined publicly known key is obviously
not a strong protection, but it protects from casual
users attempting to browse the database, and it also
protects from string scanners that scan a harddisk
for possible passwords.

On the first startup, Duplicati will encrypt the database,
using the mentioned key. You may want to delete any backup
databases that are placed in the same folder as the database.

Should you wish to decrypt the database, you can start 
Duplicati.exe with the commandline option "--unencrypted-database".
This will use the current key to decrypt the database, and save it
without encryption.

Should you wish to change the database password, simply decrypt the
database, then change the password via the environment variable,
and start Duplicati.exe as normal.

The SQLite documentation for the feature is here:
http://www.hwaci.com/sw/sqlite/see.html

Unfortunately, it does not seem as if any
of the free SQLite tools supports this.

The SQLite library found on Ubuntu
does not support encryption, so I have disabled 
this feature by default on Linux.

If a user has a special compiled version
of SQLite, they may use the environment
variables as mentioned above.

I will write this information into a wiki
page as soon asap.

git-svn-id: https://duplicati.googlecode.com/svn/trunk@498 59da171f-624f-0410-aa54-27559c288bec
2010-09-07 21:39:22 +00:00

525 lines
22 KiB
C#

#region Disclaimer / License
// Copyright (C) 2010, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// 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.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.LightDatamodel;
using Duplicati.Datamodel;
using System.Globalization;
using Duplicati.Library.Core;
namespace Duplicati.GUI
{
public partial class ApplicationSetup : Form
{
private IDataFetcherWithRelations m_connection;
private ApplicationSettings m_settings;
private bool m_isUpdating = false;
//These variables handle the worker thread size calculation
private object m_lock = new object();
private System.Threading.Thread m_thread = null;
private bool m_restartCalculator = false;
public ApplicationSetup()
{
InitializeComponent();
m_connection = new DataFetcherNested(Program.DataConnection);
m_settings = new ApplicationSettings(m_connection);
RecentDuration.SetIntervals(new List<KeyValuePair<string, string>>(
new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>(Strings.Common.OneWeek, "1W"),
new KeyValuePair<string, string>(Strings.Common.TwoWeeks, "2W"),
new KeyValuePair<string, string>(Strings.Common.OneMonth, "1M"),
new KeyValuePair<string, string>(Strings.Common.ThreeMonths, "3M"),
}));
StartupDelayDuration.SetIntervals(new List<KeyValuePair<string, string>>(
new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>(Strings.Common.NoMinutes, "0"),
new KeyValuePair<string, string>(Strings.Common.OneMinute, "1m"),
new KeyValuePair<string, string>(Strings.Common.FiveMinutes, "5m"),
new KeyValuePair<string, string>(Strings.Common.FifteenMinutes, "15m"),
new KeyValuePair<string, string>(Strings.Common.ThirtyMinutes, "30m"),
}));
try
{
LanguageSelection.Items.Clear();
LanguageSelection.Items.Add(new ComboBoxItemPair<CultureInfo>(string.Format(Strings.ApplicationSetup.DefaultLanguage, Library.Core.Utility.DefaultCulture.DisplayName), Library.Core.Utility.DefaultCulture));
System.Text.RegularExpressions.Regex cix = new System.Text.RegularExpressions.Regex("[A-z][A-z](\\-[A-z][A-z])?");
foreach(string f in System.IO.Directory.GetDirectories(Application.StartupPath))
if (cix.Match(System.IO.Path.GetFileName(f)).Length == System.IO.Path.GetFileName(f).Length)
try
{
CultureInfo ci = CultureInfo.GetCultureInfo(System.IO.Path.GetFileName(f));
LanguageSelection.Items.Add(new ComboBoxItemPair<CultureInfo>(ci.DisplayName, ci));
}
catch
{
}
}
finally
{
m_isUpdating = false;
}
try
{
EncryptionModule.Items.Clear();
foreach (Library.Interface.IEncryption e in Library.DynamicLoader.EncryptionLoader.Modules)
EncryptionModule.Items.Add(new ComboBoxItemPair<Library.Interface.IEncryption>(e.DisplayName, e));
}
catch (Exception ex)
{
MessageBox.Show(this, string.Format(Strings.ApplicationSetup.EncryptionModuleLoadError, ex.Message), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
this.Icon = Properties.Resources.TrayNormal;
}
private void RecentDuration_TextChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.RecentBackupDuration = RecentDuration.Value;
}
private void OKBtn_Click(object sender, EventArgs e)
{
foreach(TabPage tab in TabContainer.TabPages)
if (tab.Tag as Library.Interface.ISettingsControl != null)
{
try
{
if (!(tab.Tag as Library.Interface.ISettingsControl).Validate(tab.Controls[0]))
return;
(tab.Tag as Library.Interface.ISettingsControl).Leave(tab.Controls[0]);
}
catch (Exception ex)
{
MessageBox.Show(this, string.Format(Strings.ApplicationSetup.SaveExtensionError, ex), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
//Commit all changes made to the context
m_connection.CommitAllRecursive();
System.Globalization.CultureInfo newCI = System.Threading.Thread.CurrentThread.CurrentUICulture;
try
{
if (string.IsNullOrEmpty(m_settings.DisplayLanguage))
newCI = Library.Core.Utility.DefaultCulture;
else
newCI = System.Globalization.CultureInfo.GetCultureInfo(m_settings.DisplayLanguage);
}
catch
{
}
if (newCI != System.Threading.Thread.CurrentThread.CurrentUICulture)
{
MessageBox.Show(this, Strings.ApplicationSetup.LanguageChangedWarning, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
//We don't change here, because the application has loaded some
// resources already, so the switch would be partial
}
this.DialogResult = DialogResult.OK;
this.Close();
}
private void RecentDuration_ValueChanged(object sender, EventArgs e)
{
RecentDuration_TextChanged(sender, e);
}
private void TempPathBrowse_Click(object sender, EventArgs e)
{
if (BrowseTempPath.ShowDialog(this) == DialogResult.OK)
TempPath.Text = BrowseTempPath.SelectedPath;
}
private void TempPath_TextChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.TempPath = TempPath.Text;
}
private void ApplicationSetup_Load(object sender, EventArgs e)
{
try
{
this.Text = string.Format(Strings.ApplicationSetup.DialogTitle, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
m_isUpdating = true;
RecentDuration.Value = m_settings.RecentBackupDuration;
TempPath.Text = m_settings.TempPath;
UseCommonPassword.Checked = m_settings.UseCommonPassword;
CommonPassword.Text = m_settings.CommonPassword;
CommonPassword.AskToEnterNewPassword = !string.IsNullOrEmpty(CommonPassword.Text);
if (EncryptionModule.Items.Count > 0)
{
bool foundEncryption = false;
int defaultIndex = 0;
for (int i = 0; i < EncryptionModule.Items.Count; i++)
if (((ComboBoxItemPair<Library.Interface.IEncryption>)EncryptionModule.Items[i]).Value.FilenameExtension == m_settings.CommonPasswordEncryptionModule)
{
foundEncryption = true;
EncryptionModule.SelectedIndex = i;
break;
}
else if (((ComboBoxItemPair<Library.Interface.IEncryption>)EncryptionModule.Items[i]).Value.FilenameExtension == "aes")
defaultIndex = i;
if (!foundEncryption)
EncryptionModule.SelectedIndex = defaultIndex;
}
else
{
PasswordDefaultsGroup.Enabled = false;
}
SignatureCacheEnabled.Checked = m_settings.SignatureCacheEnabled;
SignatureCachePath.Text = m_settings.SignatureCachePath;
CalculateSignatureCacheSize();
StartupDelayDuration.Value = m_settings.StartupDelayDuration;
ThreadPriorityPicker.SelectedPriority = m_settings.ThreadPriorityOverride;
Bandwidth.UploadLimit = m_settings.UploadSpeedLimit;
Bandwidth.DownloadLimit = m_settings.DownloadSpeedLimit;
HideDonateButton.Checked = m_settings.HideDonateButton;
if (string.IsNullOrEmpty(m_settings.DisplayLanguage))
LanguageSelection.SelectedIndex = 0;
else
{
try
{
LanguageSelection.SelectedIndex = -1;
System.Globalization.CultureInfo cci = System.Globalization.CultureInfo.GetCultureInfo(m_settings.DisplayLanguage);
for(int i = 0; i < LanguageSelection.Items.Count; i++)
if (((ComboBoxItemPair<CultureInfo>)LanguageSelection.Items[i]).Value == cci)
{
LanguageSelection.SelectedIndex = i;
break;
}
}
catch
{
LanguageSelection.SelectedIndex = -1;
}
}
try
{
foreach (Library.Interface.ISettingsControl ic in Library.DynamicLoader.SettingsControlLoader.Modules)
{
Control c = ic.GetControl(m_settings.CreateDetachedCopy(), Datamodel.SettingExtension.GetExtensions(m_connection, ic.Key));
c.Dock = DockStyle.Fill;
TabPage tab = new TabPage();
tab.Text = ic.PageTitle;
tab.ToolTipText = ic.PageDescription;
tab.Controls.Add(c);
tab.Tag = ic;
TabContainer.TabPages.Add(tab);
}
}
catch (Exception ex)
{
MessageBox.Show(this, string.Format(Strings.ApplicationSetup.SettingControlsLoadError, ex.Message), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//Place the license page last
TabContainer.TabPages.Remove(LicenseTab);
TabContainer.TabPages.Insert(TabContainer.TabPages.Count, LicenseTab);
string licensePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "licenses");
List<Duplicati.License.LicenseEntry> licenses = Duplicati.License.LicenseReader.ReadLicenses(licensePath);
licenses.Insert(0, new Duplicati.License.LicenseEntry("Duplicati", System.IO.Path.Combine(licensePath, "duplicati-url.txt"), System.IO.Path.Combine(licensePath, "license.txt")));
licenses.Insert(0, new Duplicati.License.LicenseEntry("Acknowledgements", System.IO.Path.Combine(licensePath, "duplicati-url.txt"), System.IO.Path.Combine(licensePath, "acknowledgements.txt")));
LicenseSections.Items.Clear();
LicenseSections.Items.AddRange(licenses.ToArray());
LicenseSections.SelectedIndex = -1;
LicenseSections.SelectedIndex = 0;
}
finally
{
m_isUpdating = false;
}
}
private void UseCommonPassword_CheckedChanged(object sender, EventArgs e)
{
PasswordPanel.Enabled = UseCommonPassword.Checked;
if (m_isUpdating)
return;
m_settings.UseCommonPassword = UseCommonPassword.Checked;
if (m_settings.UseCommonPassword)
m_settings.CommonPassword = CommonPassword.Text;
else
m_settings.CommonPassword = ""; //Clear it from DB
}
private void CommonPassword_TextChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.CommonPassword = CommonPassword.Text;
}
private void SignatureCachePathBrowse_Click(object sender, EventArgs e)
{
if (BrowseSignatureCachePath.ShowDialog(this) == DialogResult.OK)
SignatureCachePath.Text = BrowseSignatureCachePath.SelectedPath;
}
private void SignatureCachePath_TextChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.SignatureCachePath = SignatureCachePath.Text;
CalculateSignatureCacheSize();
}
private void SignatureCacheEnabled_CheckedChanged(object sender, EventArgs e)
{
SignatureCachePath.Enabled = SignatureCachePathBrowse.Enabled = SignatureCacheEnabled.Checked;
if (m_isUpdating)
return;
m_settings.SignatureCacheEnabled = SignatureCacheEnabled.Checked;
CalculateSignatureCacheSize();
}
private void CacheSizeCalculator_DoWork(object sender, DoWorkEventArgs e)
{
lock (m_lock)
m_thread = System.Threading.Thread.CurrentThread;
try
{
e.Result = string.Format(Strings.ApplicationSetup.CacheSize, Library.Core.Utility.FormatSizeString(Library.Core.Utility.GetDirectorySize(System.Environment.ExpandEnvironmentVariables((string)e.Argument), null)));
}
catch (System.Threading.ThreadAbortException)
{
System.Threading.Thread.ResetAbort();
e.Cancel = true;
}
finally
{
lock (m_lock)
m_thread = null;
}
}
private void CacheSizeCalculator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
CacheSizeLabel.Text = Strings.ApplicationSetup.OperationCancelled;
else if (e.Error != null)
CacheSizeLabel.Text = e.Error.Message;
else
CacheSizeLabel.Text = (string)e.Result;
if (m_restartCalculator)
CalculateSignatureCacheSize();
}
private void CalculateSignatureCacheSize()
{
CacheSizeLabel.Text = Strings.ApplicationSetup.CalculatingCacheSize;
lock (m_lock)
if (CacheSizeCalculator.IsBusy)
{
m_restartCalculator = true;
m_thread.Abort();
}
else
{
m_restartCalculator = false;
if (SignatureCacheEnabled.Checked)
CacheSizeCalculator.RunWorkerAsync(SignatureCachePath.Text);
else
CacheSizeLabel.Text = "";
}
}
private void ClearCacheButton_Click(object sender, EventArgs e)
{
try
{
string path = System.Environment.ExpandEnvironmentVariables(SignatureCachePath.Text);
if (MessageBox.Show(this, string.Format(Strings.ApplicationSetup.ConfirmCacheDelete, path), Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning) != DialogResult.Yes)
return;
Library.Main.Interface.RemoveSignatureFiles(path);
CalculateSignatureCacheSize();
}
catch(Exception ex)
{
MessageBox.Show(this, string.Format(Strings.Common.GenericError, ex.Message), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ApplicationSetup_FormClosing(object sender, FormClosingEventArgs e)
{
lock(m_lock)
if (m_thread != null)
{
m_restartCalculator = false;
m_thread.Abort();
}
}
private void LanguageSelection_SelectedIndexChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
if (LanguageSelection.SelectedIndex == 0)
m_settings.DisplayLanguage = "";
else if (LanguageSelection.SelectedIndex > 0)
m_settings.DisplayLanguage = ((ComboBoxItemPair<CultureInfo>)LanguageSelection.SelectedItem).Value.Name;
}
private void StartupDelayDuration_ValueChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.StartupDelayDuration = StartupDelayDuration.Value;
}
private void ThreadPriorityPicker_SelectedPriorityChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.ThreadPriorityOverride = ThreadPriorityPicker.SelectedPriority;
}
private void Bandwidth_UploadLimitChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.UploadSpeedLimit = Bandwidth.UploadLimit;
}
private void Bandwidth_DownloadLimitChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.DownloadSpeedLimit = Bandwidth.DownloadLimit;
}
private void EncryptionModule_SelectedIndexChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
if (EncryptionModule.SelectedItem as ComboBoxItemPair<Library.Interface.IEncryption> != null)
m_settings.CommonPasswordEncryptionModule = ((ComboBoxItemPair<Library.Interface.IEncryption>)EncryptionModule.SelectedItem).Value.FilenameExtension;
}
public static Dictionary<string, string> GetApplicationSettings(IDataFetcher connection)
{
Dictionary<string, string> env = new ApplicationSettings(connection).CreateDetachedCopy();
//If there are any control extensions, let them modify the environement
foreach (Library.Interface.ISettingsControl ic in Library.DynamicLoader.SettingsControlLoader.Modules)
ic.BeginEdit(env, SettingExtension.GetExtensions(connection, ic.Key));
return env;
}
public static void SaveExtensionSettings(IDataFetcher connection, IDictionary<string, string> env)
{
//If there are any control extensions, let them modify the environement
foreach (Library.Interface.ISettingsControl ic in Library.DynamicLoader.SettingsControlLoader.Modules)
ic.EndEdit(env, SettingExtension.GetExtensions(connection, ic.Key));
}
private void HideDonateButton_CheckedChanged(object sender, EventArgs e)
{
if (m_isUpdating)
return;
m_settings.HideDonateButton = HideDonateButton.Checked;
}
private void LicenseSections_SelectedIndexChanged(object sender, EventArgs e)
{
if (LicenseSections.SelectedItem as Duplicati.License.LicenseEntry != null)
{
Duplicati.License.LicenseEntry l = LicenseSections.SelectedItem as Duplicati.License.LicenseEntry;
LicenseText.Text = l.License;
LicenseLink.Visible = !string.IsNullOrEmpty(l.Url);
}
}
private void LicenseLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (LicenseSections.SelectedItem as Duplicati.License.LicenseEntry != null)
{
Duplicati.License.LicenseEntry l = LicenseSections.SelectedItem as Duplicati.License.LicenseEntry;
if (!string.IsNullOrEmpty(l.Url))
UrlUtillity.OpenUrl(l.Url);
}
}
}
}