diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e02419e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +custom: http://paypal.com/cgi-bin/webscr?cmd=_donations&business=rookiestyle%40gmx%2enet¤cy_code=EUR&lc=en_DE&item_name=LockAssist diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4c06a48 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Found a bug? Report it to get it fixed +title: '' +labels: bug +assignees: '' + +--- + +## Overview +[TIP]: # (DO NOT include screenshots of your actual database) +[TIP]: # (DO NOT post sensitive data of any kind) +[NOTE]: # (Give a BRIEF summary about your problem) + + +## Steps to Reproduce +[NOTE]: # (Provide a simple set of steps to reproduce this bug) +1. +2. +3. + +## Expected Behavior +[NOTE]: # (Describe how you expect the plugin to behave) + + +## Actual Behavior +[NOTE]: # (Describe how the plugin actually behaves) + + +## Context +[NOTE]: # (Provide any additional information you may have) +OS: +KeePass Version: +Plugin Version: + +[NOTE]: # (If possible, please attach a debug file. Have a look at the wiki for details.) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..173b389 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this plugin +title: '' +labels: enhancement +assignees: '' + +--- + +## Summary +[TIP]: # (DO NOT include screenshots of your actual database) +[TIP]: # (DO NOT post sensitive data of any kind) +[NOTE]: # (Provide a brief overview of what the new feature is all about) + + +## Added value +[NOTE]: # (Describe how you and other users would benefit from this feature) + + +## Example +[NOTE]: # (Show a picture or a mock-up if applicable) diff --git a/.github/ISSUE_TEMPLATE/translation.md b/.github/ISSUE_TEMPLATE/translation.md new file mode 100644 index 0000000..0f1a671 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation.md @@ -0,0 +1,17 @@ +--- +name: Translation +about: New or updated translation +title: 'Translation: ' +labels: translation +assignees: '' + +--- + +[NOTE]: # (Please do not forget to attach the translation file) +Language: +- [ ] New translation +- [ ] Update existing translation +- [ ] If updated: Only added missing Texts +- [ ] All relevant texts translated + +[NOTE]: # (A text is relevant if the standard text in english language is NOT sufficient) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfcfd56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/LockAssist.cs b/LockAssist.cs new file mode 100644 index 0000000..f2bcf58 --- /dev/null +++ b/LockAssist.cs @@ -0,0 +1,254 @@ +using KeePass.Plugins; +using KeePassLib; +using System.Collections.Generic; +using System.Windows.Forms; + +using PluginTranslation; +using PluginTools; +using System.Drawing; + +namespace LockAssist +{ + public class LockAssistOptions + { + public IPluginHost host; + public bool FirstTime = false; + public bool QUActive = false; + public bool DBSpecific = false; + public bool UsePassword = true; + public int Length = 4; + public bool FromEnd = true; + public bool SLMinimize = false; + public int SLSeconds = 60; + public bool SLIdle = true; + public bool SLIdleActive { get { return SLIdle && (SLSeconds > 0); } } + public List SLExcludeForms = new List() + { + "AboutForm", "AutoTypeCtxForm", "CharPickerForm", "ColumnsForm", + "HelpSourceForm", "KeyPromptForm", "LanguageForm", "PluginsForm", + "PwGeneratorForm", "UpdateCheckForm" + }; + public bool LockWorkspace = false; + + public bool ConfigChanged(LockAssistOptions comp, bool CheckDBSpecific) + { + if (QUActive != comp.QUActive) return true; + if (CheckDBSpecific && (DBSpecific != comp.DBSpecific)) return true; + if (UsePassword != comp.UsePassword) return true; + if (Length != comp.Length) return true; + if (FromEnd != comp.FromEnd) return true; + if (SLMinimize != comp.SLMinimize) return true; + if (SLIdle != comp.SLIdle) return true; + if (SLSeconds != comp.SLSeconds) return true; + if (LockWorkspace != comp.LockWorkspace) return true; + return false; + } + + public bool CopyFrom(LockAssistOptions NewOptions) + { + bool SwitchToNoDBSpecific = DBSpecific && !NewOptions.DBSpecific; + DBSpecific = NewOptions.DBSpecific; + QUActive = NewOptions.QUActive; + UsePassword = NewOptions.UsePassword; + Length = NewOptions.Length; + FromEnd = NewOptions.FromEnd; + SLMinimize = NewOptions.SLMinimize; + SLIdle = NewOptions.SLIdle; + SLSeconds = NewOptions.SLSeconds; + LockWorkspace = NewOptions.LockWorkspace; + return SwitchToNoDBSpecific; + } + + public void ReadConfig() + { + ReadConfig(host.Database); + } + + public void ReadConfig(PwDatabase db) + { + FirstTime = host.CustomConfig.GetBool("LockAssist.FirstTime", true); + DBSpecific = (db != null) && (db.IsOpen) && db.CustomData.Exists("LockAssist.UsePassword"); + if (DBSpecific) + { + QUActive = db.CustomData.Get("LockAssist.Active") == "true"; + UsePassword = db.CustomData.Get("LockAssist.UsePassword") == "true"; + if (!int.TryParse(db.CustomData.Get("LockAssist.KeyLength"), out Length)) Length = 4; + FromEnd = db.CustomData.Get("LockAssist.KeyFromEnd") == "false"; + } + else + { + QUActive = host.CustomConfig.GetBool("LockAssist.Active", false); + UsePassword = host.CustomConfig.GetBool("LockAssist.UsePassword", true); + Length = (int)host.CustomConfig.GetLong("LockAssist.KeyLength", 4); + FromEnd = host.CustomConfig.GetBool("LockAssist.KeyFromEnd", true); + } + + SLIdle = host.CustomConfig.GetBool("LockAssist.SoftlockMode.HideOnIdle", true); + SLSeconds = (int)host.CustomConfig.GetLong("LockAssist.SoftlockMode.Seconds", 0); + SLMinimize = host.CustomConfig.GetBool("LockAssist.SoftlockMode.HideOnMinimize", false); + string excludeForms = host.CustomConfig.GetString("LockAssist.SoftlockMode.ExcludeForms", ""); + if (!string.IsNullOrEmpty(excludeForms)) + { + SLExcludeForms.Clear(); + SLExcludeForms.AddRange(excludeForms.Split(new char[1] { ',' })); + } + LockWorkspace = host.CustomConfig.GetBool("LockAssist.LockWorkspace", false); + } + + public void WriteConfig() + { + WriteConfig(host.Database); + } + + public void WriteConfig(PwDatabase db) + { + if (DBSpecific) + { + host.Database.CustomData.Set("LockAssist.Active", QUActive ? "true" : "false"); + host.Database.CustomData.Set("LockAssist.UsePassword", UsePassword ? "true" : "false"); + host.Database.CustomData.Set("LockAssist.KeyLength", Length.ToString()); + host.Database.CustomData.Set("LockAssist.KeyFromEnd", FromEnd ? "true" : "false"); + } + else + { + host.CustomConfig.SetBool("LockAssist.Active", QUActive); + host.CustomConfig.SetBool("LockAssist.UsePassword", UsePassword); + host.CustomConfig.SetLong("LockAssist.KeyLength", Length); + host.CustomConfig.SetBool("LockAssist.KeyFromEnd", FromEnd); + DeleteDBConfig(); + } + host.CustomConfig.SetBool("LockAssist.SoftlockMode.HideOnIdle", SLIdle); + host.CustomConfig.SetLong("LockAssist.SoftlockMode.Seconds", SLSeconds); + host.CustomConfig.SetBool("LockAssist.SoftlockMode.HideOnMinimize", SLMinimize); + string excludeForms = string.Empty; + foreach (string form in SLExcludeForms) + excludeForms += "," + form; + excludeForms = excludeForms.Substring(1); + host.CustomConfig.SetString("LockAssist.SoftlockMode.ExcludeForms", excludeForms); + host.CustomConfig.SetBool("LockAssist.LockWorkspace", LockWorkspace); + } + + public void DeleteDBConfig() + { + bool deleted = host.Database.CustomData.Remove("LockAssist.Active"); + deleted |= deleted = host.Database.CustomData.Remove("LockAssist.UsePassword"); + deleted |= host.Database.CustomData.Remove("LockAssist.KeyLength"); + deleted |= host.Database.CustomData.Remove("LockAssist.KeyFromEnd"); + if (deleted) + { + host.MainWindow.UpdateUI(false, null, false, null, false, null, true); + host.Database.Modified = true; + } + } + } + + public partial class LockAssistExt : Plugin, IMessageFilter + { + private static IPluginHost m_host = null; + private QuickUnlockKeyProv m_kp = null; + private ToolStripMenuItem m_menu = null; + private static bool Terminated { get { return m_host == null; } } + + private LockAssistOptions m_options; + + public override bool Initialize(IPluginHost host) + { + if (m_host != null) Terminate(); + m_host = host; + + PluginTranslate.Init(this, KeePass.Program.Translation.Properties.Iso6391Code); + Tools.DefaultCaption = PluginTranslate.PluginName; + + m_menu = new ToolStripMenuItem(); + m_menu.Text = PluginTranslate.PluginName + "..."; + m_menu.Click += (o, e) => Tools.ShowOptions(); + m_menu.Image = m_host.MainWindow.ClientIcons.Images[51]; + m_host.MainWindow.ToolsMenu.DropDownItems.Add(m_menu); + + Tools.OptionsFormShown += OptionsFormShown; + Tools.OptionsFormClosed += OptionsFormClosed; + m_options = new LockAssistOptions(); + m_options.host = m_host; + m_options.ReadConfig(); + + QuickUnlock_Init(); + Softlock_Init(); + LockWorkspace_Init(); + + return true; + } + + public override void Terminate() + { + Application.RemoveMessageFilter(this); + Tools.OptionsFormShown -= OptionsFormShown; + Tools.OptionsFormClosed -= OptionsFormClosed; + + QuickUnlock_Terminate(); + Softlock_Terminate(); + LockWorkspace_Terminate(); + + m_host.MainWindow.ToolsMenu.DropDownItems.Remove(m_menu); + m_host = null; + } + + #region Options + private void OptionsFormShown(object sender, Tools.OptionsFormsEventArgs e) + { + m_options.ReadConfig(); + OptionsForm options = new OptionsForm(); + options.SetOptions(m_options); + Tools.AddPluginToOptionsForm(this, options); + } + + private void OptionsFormClosed(object sender, Tools.OptionsFormsEventArgs e) + { + if (e.form.DialogResult != DialogResult.OK) return; + bool shown = false; + OptionsForm options = (OptionsForm)Tools.GetPluginFromOptions(this, out shown); + if (!shown) return; + LockAssistOptions NewOptions = options.GetOptions(); + bool changedConfig = m_options.ConfigChanged(NewOptions, false); + bool changedConfigTotal = m_options.ConfigChanged(NewOptions, true); + if (m_SLHide) + { + if (changedConfig) Tools.ShowError(PluginTranslate.OptionsNotSavedSoftlock); + return; + } + if (m_options.LockWorkspace != NewOptions.LockWorkspace) + ActivateNewLockWorkspace(NewOptions.LockWorkspace); + bool SwitchToNoDBSpecific = m_options.CopyFrom(NewOptions); + CheckSoftlockMode(); + if (SwitchToNoDBSpecific) + { + if (Tools.AskYesNo(PluginTranslate.OptionsSwitchDBToGeneral) == DialogResult.Yes) + { + //Remove DB specific configuration + m_options.DeleteDBConfig(); + m_options.ReadConfig(); + } + else + { + //Make current configuration the new global configuration + m_options.WriteConfig(); + } + } + else + m_options.WriteConfig(); + CheckSoftlockMode(); + if ((changedConfigTotal && m_options.DBSpecific) || SwitchToNoDBSpecific) + { + m_host.MainWindow.UpdateUI(false, null, false, null, false, null, true); + m_host.Database.Modified = true; + } + } + #endregion + + public override string UpdateUrl + { + get { return "https://firebasestorage.googleapis.com/v0/b/rookiestyle-43398.appspot.com/o/versioninfo.txt?alt=media&token=89da60c0-f331-4334-94bb-0dccf617818f"; } + } + + public override Image SmallIcon { get { return m_menu.Image; } } + } +} \ No newline at end of file diff --git a/LockAssist.csproj b/LockAssist.csproj new file mode 100644 index 0000000..c550771 --- /dev/null +++ b/LockAssist.csproj @@ -0,0 +1,76 @@ + + + + + Release + AnyCPU + {4712D887-6685-4CB1-B67B-E981119FE3D2} + Library + Properties + LockAssist + LockAssist + v2.0 + 512 + true + + + + true + full + false + ..\..\KeePass\source\Build\KeePass\Debug\Plugins\ + TRACE;DEBUG + prompt + 4 + true + false + + + pdbonly + true + ..\..\KeePass\source\Build\KeePass\Release\Plugins\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + Form + + + UnlockForm.cs + + + + + + UserControl + + + OptionsForm.cs + + + + + {10938016-dee2-4a25-9a5a-8fd3444379ca} + KeePass + + + + + call "$(ProjectDir)plgxcreate.cmd" +call "$(ProjectDir)translationcopy.cmd" + + \ No newline at end of file diff --git a/LockAssist.sln b/LockAssist.sln new file mode 100644 index 0000000..c702dc7 --- /dev/null +++ b/LockAssist.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2048 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LockAssist", "src\LockAssist.csproj", "{4712D887-6685-4CB1-B67B-E981119FE3D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\_KeePass_Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2ACAE5D-E1C7-459F-B770-A5A8AEFE0BEB}" + ProjectSection(SolutionItems) = preProject + plgxcreate.cmd = plgxcreate.cmd + version.info = version.info + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + ReleasePlgx|Any CPU = ReleasePlgx|Any CPU + ReleasePlgx|Mixed Platforms = ReleasePlgx|Mixed Platforms + ReleasePlgx|Win32 = ReleasePlgx|Win32 + ReleasePlgx|x64 = ReleasePlgx|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Win32.ActiveCfg = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|Win32.Build.0 = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.Debug|x64.Build.0 = Debug|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Any CPU.ActiveCfg = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Any CPU.Build.0 = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Mixed Platforms.ActiveCfg = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Mixed Platforms.Build.0 = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Win32.ActiveCfg = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|Win32.Build.0 = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|x64.ActiveCfg = ReleasePlgx|Any CPU + {4712D887-6685-4CB1-B67B-E981119FE3D2}.ReleasePlgx|x64.Build.0 = ReleasePlgx|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Win32.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Win32.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|x64.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Mixed Platforms.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Mixed Platforms.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Win32.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|Win32.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|x64.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.ReleasePlgx|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6D5548B5-0461-4635-862A-3283E03A5485} + EndGlobalSection +EndGlobal diff --git a/OptionsForm.Designer.cs b/OptionsForm.Designer.cs new file mode 100644 index 0000000..c12502a --- /dev/null +++ b/OptionsForm.Designer.cs @@ -0,0 +1,371 @@ +namespace LockAssist +{ + partial class OptionsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabQuickUnlock = new System.Windows.Forms.TabPage(); + this.cbPINDBSpecific = new System.Windows.Forms.CheckBox(); + this.tbModeExplain = new System.Windows.Forms.TextBox(); + this.panel2 = new System.Windows.Forms.Panel(); + this.cbActive = new System.Windows.Forms.CheckBox(); + this.lQUPINLength = new System.Windows.Forms.Label(); + this.lQUMode = new System.Windows.Forms.Label(); + this.rbPINEnd = new System.Windows.Forms.RadioButton(); + this.rbPINFront = new System.Windows.Forms.RadioButton(); + this.tbPINLength = new System.Windows.Forms.TextBox(); + this.cbPINMode = new System.Windows.Forms.ComboBox(); + this.tabSoftlock = new System.Windows.Forms.TabPage(); + this.tbSoftlockExplain = new System.Windows.Forms.TextBox(); + this.panel1 = new System.Windows.Forms.Panel(); + this.cbSLIdle = new System.Windows.Forms.CheckBox(); + this.cbSLOnMinimize = new System.Windows.Forms.CheckBox(); + this.tSLIdleSeconds = new System.Windows.Forms.TextBox(); + this.tabAdditional = new System.Windows.Forms.TabPage(); + this.tbLockWorkspace = new System.Windows.Forms.TextBox(); + this.panel3 = new System.Windows.Forms.Panel(); + this.cbLockWorkspace = new System.Windows.Forms.CheckBox(); + this.tabControl1.SuspendLayout(); + this.tabQuickUnlock.SuspendLayout(); + this.panel2.SuspendLayout(); + this.tabSoftlock.SuspendLayout(); + this.panel1.SuspendLayout(); + this.tabAdditional.SuspendLayout(); + this.panel3.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabQuickUnlock); + this.tabControl1.Controls.Add(this.tabSoftlock); + this.tabControl1.Controls.Add(this.tabAdditional); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(480, 450); + this.tabControl1.TabIndex = 6; + // + // tabQuickUnlock + // + this.tabQuickUnlock.BackColor = System.Drawing.Color.Transparent; + this.tabQuickUnlock.Controls.Add(this.cbPINDBSpecific); + this.tabQuickUnlock.Controls.Add(this.tbModeExplain); + this.tabQuickUnlock.Controls.Add(this.panel2); + this.tabQuickUnlock.Location = new System.Drawing.Point(4, 29); + this.tabQuickUnlock.Name = "tabQuickUnlock"; + this.tabQuickUnlock.Padding = new System.Windows.Forms.Padding(3); + this.tabQuickUnlock.Size = new System.Drawing.Size(472, 417); + this.tabQuickUnlock.TabIndex = 0; + this.tabQuickUnlock.Text = "Quick Unlock settings"; + this.tabQuickUnlock.UseVisualStyleBackColor = true; + // + // cbPINDBSpecific + // + this.cbPINDBSpecific.AutoSize = true; + this.cbPINDBSpecific.Location = new System.Drawing.Point(11, 316); + this.cbPINDBSpecific.Name = "cbPINDBSpecific"; + this.cbPINDBSpecific.Size = new System.Drawing.Size(205, 24); + this.cbPINDBSpecific.TabIndex = 5; + this.cbPINDBSpecific.Text = "Settings are DB specific"; + this.cbPINDBSpecific.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + this.cbPINDBSpecific.UseVisualStyleBackColor = true; + // + // tbModeExplain + // + this.tbModeExplain.Dock = System.Windows.Forms.DockStyle.Top; + this.tbModeExplain.ForeColor = System.Drawing.SystemColors.WindowText; + this.tbModeExplain.Location = new System.Drawing.Point(3, 183); + this.tbModeExplain.Multiline = true; + this.tbModeExplain.Name = "tbModeExplain"; + this.tbModeExplain.ReadOnly = true; + this.tbModeExplain.Size = new System.Drawing.Size(466, 116); + this.tbModeExplain.TabIndex = 34; + this.tbModeExplain.TabStop = false; + this.tbModeExplain.Text = "Requirements for mode \'Database password\'\r\n - Database masterkey contains a passw" + + "ord\r\n - Option \'Remember master password\' is active\r\n\r\nQuick Unlock Entry will b" + + "e used as fallback"; + // + // panel2 + // + this.panel2.Controls.Add(this.cbActive); + this.panel2.Controls.Add(this.lQUPINLength); + this.panel2.Controls.Add(this.lQUMode); + this.panel2.Controls.Add(this.rbPINEnd); + this.panel2.Controls.Add(this.rbPINFront); + this.panel2.Controls.Add(this.tbPINLength); + this.panel2.Controls.Add(this.cbPINMode); + this.panel2.Dock = System.Windows.Forms.DockStyle.Top; + this.panel2.Location = new System.Drawing.Point(3, 3); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(466, 180); + this.panel2.TabIndex = 35; + // + // cbActive + // + this.cbActive.AutoSize = true; + this.cbActive.Location = new System.Drawing.Point(8, 11); + this.cbActive.Name = "cbActive"; + this.cbActive.Size = new System.Drawing.Size(182, 24); + this.cbActive.TabIndex = 42; + this.cbActive.Text = "Enable Quick Unlock"; + this.cbActive.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + this.cbActive.UseVisualStyleBackColor = true; + // + // lQUPINLength + // + this.lQUPINLength.AutoSize = true; + this.lQUPINLength.Location = new System.Drawing.Point(4, 86); + this.lQUPINLength.Name = "lQUPINLength"; + this.lQUPINLength.Size = new System.Drawing.Size(87, 20); + this.lQUPINLength.TabIndex = 41; + this.lQUPINLength.Text = "PIN length:"; + // + // lQUMode + // + this.lQUMode.AutoSize = true; + this.lQUMode.Location = new System.Drawing.Point(4, 44); + this.lQUMode.Name = "lQUMode"; + this.lQUMode.Size = new System.Drawing.Size(53, 20); + this.lQUMode.TabIndex = 40; + this.lQUMode.Text = "Mode:"; + // + // rbPINEnd + // + this.rbPINEnd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.rbPINEnd.AutoSize = true; + this.rbPINEnd.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.rbPINEnd.Location = new System.Drawing.Point(268, 145); + this.rbPINEnd.Name = "rbPINEnd"; + this.rbPINEnd.Size = new System.Drawing.Size(194, 24); + this.rbPINEnd.TabIndex = 39; + this.rbPINEnd.TabStop = true; + this.rbPINEnd.Text = "Use {0} last characters"; + this.rbPINEnd.UseVisualStyleBackColor = true; + // + // rbPINFront + // + this.rbPINFront.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.rbPINFront.AutoSize = true; + this.rbPINFront.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.rbPINFront.Location = new System.Drawing.Point(268, 115); + this.rbPINFront.Name = "rbPINFront"; + this.rbPINFront.Size = new System.Drawing.Size(195, 24); + this.rbPINFront.TabIndex = 38; + this.rbPINFront.TabStop = true; + this.rbPINFront.Text = "Use {0} first characters"; + this.rbPINFront.UseVisualStyleBackColor = true; + // + // tbPINLength + // + this.tbPINLength.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tbPINLength.Location = new System.Drawing.Point(362, 83); + this.tbPINLength.MaxLength = 3; + this.tbPINLength.Name = "tbPINLength"; + this.tbPINLength.Size = new System.Drawing.Size(100, 26); + this.tbPINLength.TabIndex = 37; + this.tbPINLength.Tag = "32"; + this.tbPINLength.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.tbPINLength.TextChanged += new System.EventHandler(this.tbPINLength_TextChanged); + // + // cbPINMode + // + this.cbPINMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cbPINMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbPINMode.FormattingEnabled = true; + this.cbPINMode.Items.AddRange(new object[] { + "Quick Unlock entry only", + "Database password"}); + this.cbPINMode.Location = new System.Drawing.Point(109, 41); + this.cbPINMode.Name = "cbPINMode"; + this.cbPINMode.Size = new System.Drawing.Size(353, 28); + this.cbPINMode.TabIndex = 36; + this.cbPINMode.SelectedIndexChanged += new System.EventHandler(this.cbPINMode_SelectedIndexChanged); + // + // tabSoftlock + // + this.tabSoftlock.Controls.Add(this.tbSoftlockExplain); + this.tabSoftlock.Controls.Add(this.panel1); + this.tabSoftlock.Location = new System.Drawing.Point(4, 29); + this.tabSoftlock.Name = "tabSoftlock"; + this.tabSoftlock.Padding = new System.Windows.Forms.Padding(3); + this.tabSoftlock.Size = new System.Drawing.Size(472, 417); + this.tabSoftlock.TabIndex = 1; + this.tabSoftlock.Text = "Privacy Mode configuration"; + this.tabSoftlock.UseVisualStyleBackColor = true; + // + // tbSoftlockExplain + // + this.tbSoftlockExplain.Dock = System.Windows.Forms.DockStyle.Top; + this.tbSoftlockExplain.Location = new System.Drawing.Point(3, 73); + this.tbSoftlockExplain.Multiline = true; + this.tbSoftlockExplain.Name = "tbSoftlockExplain"; + this.tbSoftlockExplain.ReadOnly = true; + this.tbSoftlockExplain.Size = new System.Drawing.Size(466, 210); + this.tbSoftlockExplain.TabIndex = 35; + this.tbSoftlockExplain.TabStop = false; + this.tbSoftlockExplain.Text = "Soft Lock explanation"; + // + // panel1 + // + this.panel1.Controls.Add(this.cbSLIdle); + this.panel1.Controls.Add(this.cbSLOnMinimize); + this.panel1.Controls.Add(this.tSLIdleSeconds); + this.panel1.Dock = System.Windows.Forms.DockStyle.Top; + this.panel1.Location = new System.Drawing.Point(3, 3); + this.panel1.Margin = new System.Windows.Forms.Padding(0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(466, 70); + this.panel1.TabIndex = 36; + // + // cbSLIdle + // + this.cbSLIdle.AutoSize = true; + this.cbSLIdle.Location = new System.Drawing.Point(8, 11); + this.cbSLIdle.Name = "cbSLIdle"; + this.cbSLIdle.Size = new System.Drawing.Size(179, 24); + this.cbSLIdle.TabIndex = 17; + this.cbSLIdle.Text = "Seconds of inactivity"; + this.cbSLIdle.UseVisualStyleBackColor = true; + // + // cbSLOnMinimize + // + this.cbSLOnMinimize.AutoSize = true; + this.cbSLOnMinimize.Location = new System.Drawing.Point(8, 41); + this.cbSLOnMinimize.Name = "cbSLOnMinimize"; + this.cbSLOnMinimize.Size = new System.Drawing.Size(215, 24); + this.cbSLOnMinimize.TabIndex = 16; + this.cbSLOnMinimize.Text = "Privacy mode on Minimize"; + this.cbSLOnMinimize.UseVisualStyleBackColor = true; + // + // tSLIdleSeconds + // + this.tSLIdleSeconds.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tSLIdleSeconds.Location = new System.Drawing.Point(363, 7); + this.tSLIdleSeconds.MaxLength = 5; + this.tSLIdleSeconds.Name = "tSLIdleSeconds"; + this.tSLIdleSeconds.Size = new System.Drawing.Size(100, 26); + this.tSLIdleSeconds.TabIndex = 14; + this.tSLIdleSeconds.Tag = "9999"; + this.tSLIdleSeconds.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // tabAdditional + // + this.tabAdditional.Controls.Add(this.tbLockWorkspace); + this.tabAdditional.Controls.Add(this.panel3); + this.tabAdditional.Location = new System.Drawing.Point(4, 29); + this.tabAdditional.Name = "tabAdditional"; + this.tabAdditional.Padding = new System.Windows.Forms.Padding(3); + this.tabAdditional.Size = new System.Drawing.Size(472, 417); + this.tabAdditional.TabIndex = 2; + this.tabAdditional.Text = "Additional"; + this.tabAdditional.UseVisualStyleBackColor = true; + // + // tbLockWorkspace + // + this.tbLockWorkspace.Dock = System.Windows.Forms.DockStyle.Top; + this.tbLockWorkspace.ForeColor = System.Drawing.SystemColors.WindowText; + this.tbLockWorkspace.Location = new System.Drawing.Point(3, 53); + this.tbLockWorkspace.Multiline = true; + this.tbLockWorkspace.Name = "tbLockWorkspace"; + this.tbLockWorkspace.ReadOnly = true; + this.tbLockWorkspace.Size = new System.Drawing.Size(466, 237); + this.tbLockWorkspace.TabIndex = 36; + this.tbLockWorkspace.TabStop = false; + // + // panel3 + // + this.panel3.Controls.Add(this.cbLockWorkspace); + this.panel3.Dock = System.Windows.Forms.DockStyle.Top; + this.panel3.Location = new System.Drawing.Point(3, 3); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(466, 50); + this.panel3.TabIndex = 37; + // + // cbLockWorkspace + // + this.cbLockWorkspace.AutoSize = true; + this.cbLockWorkspace.Location = new System.Drawing.Point(8, 11); + this.cbLockWorkspace.Name = "cbLockWorkspace"; + this.cbLockWorkspace.Size = new System.Drawing.Size(203, 24); + this.cbLockWorkspace.TabIndex = 42; + this.cbLockWorkspace.Text = "Global Lock Workspace"; + this.cbLockWorkspace.TextAlign = System.Drawing.ContentAlignment.BottomLeft; + this.cbLockWorkspace.UseVisualStyleBackColor = true; + // + // OptionsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Transparent; + this.Controls.Add(this.tabControl1); + this.Name = "OptionsForm"; + this.Size = new System.Drawing.Size(480, 450); + this.Load += new System.EventHandler(this.UnlockOptions_Load); + this.tabControl1.ResumeLayout(false); + this.tabQuickUnlock.ResumeLayout(false); + this.tabQuickUnlock.PerformLayout(); + this.panel2.ResumeLayout(false); + this.panel2.PerformLayout(); + this.tabSoftlock.ResumeLayout(false); + this.tabSoftlock.PerformLayout(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.tabAdditional.ResumeLayout(false); + this.tabAdditional.PerformLayout(); + this.panel3.ResumeLayout(false); + this.panel3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabQuickUnlock; + private System.Windows.Forms.CheckBox cbPINDBSpecific; + private System.Windows.Forms.TabPage tabSoftlock; + private System.Windows.Forms.TextBox tbSoftlockExplain; + private System.Windows.Forms.TextBox tbModeExplain; + private System.Windows.Forms.Panel panel1; + public System.Windows.Forms.TextBox tSLIdleSeconds; + public System.Windows.Forms.CheckBox cbSLIdle; + public System.Windows.Forms.CheckBox cbSLOnMinimize; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.CheckBox cbActive; + private System.Windows.Forms.Label lQUPINLength; + private System.Windows.Forms.Label lQUMode; + private System.Windows.Forms.RadioButton rbPINEnd; + private System.Windows.Forms.RadioButton rbPINFront; + private System.Windows.Forms.TextBox tbPINLength; + internal System.Windows.Forms.ComboBox cbPINMode; + private System.Windows.Forms.TabPage tabAdditional; + private System.Windows.Forms.TextBox tbLockWorkspace; + private System.Windows.Forms.Panel panel3; + internal System.Windows.Forms.CheckBox cbLockWorkspace; + } +} \ No newline at end of file diff --git a/OptionsForm.cs b/OptionsForm.cs new file mode 100644 index 0000000..3b60ded --- /dev/null +++ b/OptionsForm.cs @@ -0,0 +1,153 @@ +using System; +using System.Windows.Forms; + +using KeePass; +using KeePassLib; +using KeePass.UI; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public partial class OptionsForm : UserControl + { + private bool FirstTime = false; + + public OptionsForm() + { + InitializeComponent(); + + Text = PluginTranslate.PluginName; + cbActive.Text = PluginTranslate.Active; + tabQuickUnlock.Text = PluginTranslate.OptionsQUSettings; + lQUMode.Text = PluginTranslate.OptionsQUMode; + lQUPINLength.Text = PluginTranslate.OptionsQUPINLength; + tbModeExplain.Lines = PluginTranslate.OptionsQUReqInfoDB.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); ; + cbPINDBSpecific.Text = PluginTranslate.OptionsQUSettingsPerDB; + cbPINMode.Items.Clear(); + cbPINMode.Items.AddRange(new string[] { PluginTranslate.OptionsQUModeEntry, PluginTranslate.OptionsQUModeDatabasePW }); + tabSoftlock.Text = PluginTranslate.OptionsSLSettings; + cbSLIdle.Text = PluginTranslate.OptionsSLSecondsToActivate; + cbSLOnMinimize.Text = PluginTranslate.OptionsSLMinimize; + tbSoftlockExplain.Lines = PluginTranslate.OptionsSLInfo.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); ; + tabAdditional.Text = KeePass.Resources.KPRes.LockWorkspace; + cbLockWorkspace.Text = string.Format(PluginTranslate.OptionsLockWorkspace, KeePass.Resources.KPRes.LockWorkspace, KeePass.Resources.KPRes.LockMenuUnlock); + tbLockWorkspace.Lines = PluginTranslate.OptionsLockWorkspaceDesc.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); ; + } + + public void SetOptions(LockAssistOptions options) + { + cbActive.Checked = options.QUActive; + cbPINMode.SelectedIndex = options.UsePassword ? 1 : 0; + tbPINLength.Text = options.Length.ToString(); + rbPINEnd.Checked = options.FromEnd; + rbPINFront.Checked = !options.FromEnd; + cbPINDBSpecific.Checked = options.DBSpecific; + cbSLIdle.Checked = options.SLIdle && (options.SLSeconds > 0); + if (options.SLSeconds < 1) options.SLSeconds = 60; + //tSLIdleSeconds.Text = "60"; + tSLIdleSeconds.Text = options.SLSeconds.ToString(); + cbSLOnMinimize.Checked = options.SLMinimize; + FirstTime = options.FirstTime; + cbLockWorkspace.Checked = options.LockWorkspace; + } + + public LockAssistOptions GetOptions() + { + LockAssistOptions options = new LockAssistOptions(); + options.QUActive = cbActive.Checked; + options.UsePassword = cbPINMode.SelectedIndex == 1; + if (!int.TryParse(tbPINLength.Text, out options.Length)) options.Length = 4; + options.FromEnd = rbPINEnd.Checked; + options.DBSpecific = cbPINDBSpecific.Checked; + if (!int.TryParse(tSLIdleSeconds.Text, out options.SLSeconds)) options.SLSeconds = 60; + if (options.SLSeconds <= 0) options.SLSeconds = 60; + options.SLIdle = cbSLIdle.Checked; + options.SLMinimize = cbSLOnMinimize.Checked; + options.LockWorkspace = cbLockWorkspace.Checked; + return options; + } + + private void tbPINLength_TextChanged(object sender, EventArgs e) + { + int len = 0; + if (!int.TryParse(tbPINLength.Text, out len)) len = 4; + if (len < 1) len = 1; + if (len > 32) len = 32; + rbPINFront.Text = string.Format(PluginTranslate.OptionsQUUseFirst, len.ToString()); + rbPINEnd.Text = string.Format(PluginTranslate.OptionsQUUseLast, len.ToString()); + } + + private void cbPINMode_SelectedIndexChanged(object sender, EventArgs e) + { + lQUMode.ForeColor = System.Drawing.SystemColors.ControlText; + if (cbPINMode.SelectedIndex == 0) + { + PwEntry check = LockAssistExt.GetQuickUnlockEntry(Program.MainForm.ActiveDatabase); + if (check != null) return; + if (!FirstTime && (Tools.AskYesNo(PluginTranslate.OptionsQUEntryCreate) == DialogResult.No)) + { + cbPINMode.SelectedIndex = 1; + } + else + { + check = new PwEntry(true, true); + Program.MainForm.ActiveDatabase.RootGroup.AddEntry(check, true); + check.Strings.Set(PwDefs.TitleField, new KeePassLib.Security.ProtectedString(false, QuickUnlockKeyProv.KeyProviderName)); + Tools.ShowInfo(PluginTranslate.OptionsQUEntryCreated); + ShowQuickUnlockEntry(Program.MainForm.ActiveDatabase, check.Uuid); + } + return; + } + if (Program.Config.Security.MasterPassword.RememberWhileOpen) return; + lQUMode.ForeColor = System.Drawing.Color.Red; + if (FirstTime || (cbPINMode.SelectedIndex == 0)) return; + Tools.ShowInfo(PluginTranslate.OptionsQUInfoRememberPassword); + } + + private void tbValidating(object sender, System.ComponentModel.CancelEventArgs e) + { + int len = 0; + if (!int.TryParse((sender as TextBox).Text, out len)) + { + if ((sender as TextBox).Name == "tbPinLength") len = 4; + else len = 60; + } + if ((sender as TextBox).Name == "tbPinLength") len = Math.Max(1, len); + if ((sender as TextBox).Name != "tbPinLength") len = Math.Max(0, len); + int max = int.Parse((string)(sender as TextBox).Tag); + if (len > max) len = max; + if ((sender as TextBox).Text != len.ToString()) (sender as TextBox).Text = len.ToString(); + } + + private void ShowQuickUnlockEntry(PwDatabase db, PwUuid qu) + { + Program.MainForm.UpdateUI(false, null, false, db.RootGroup, true, db.RootGroup, true); + Program.MainForm.EnsureVisibleEntry(qu); + + ListView lv = (Program.MainForm.Controls.Find("m_lvEntries", true)[0] as ListView); + foreach (ListViewItem lvi in lv.Items) + { + PwListItem li = (lvi.Tag as PwListItem); + if (li == null) continue; + + PwEntry pe = li.Entry; + if (pe.Uuid != qu) continue; + lv.FocusedItem = lvi; + ToolStripItem[] tsmi = Program.MainForm.EntryContextMenu.Items.Find("m_ctxEntryEdit", false); + if (tsmi != null) tsmi[0].PerformClick(); + break; + } + } + + private void UnlockOptions_Load(object sender, EventArgs e) + { + if ((Program.MainForm.ActiveDatabase == null) || !Program.MainForm.ActiveDatabase.IsOpen) + { + cbPINDBSpecific.Enabled = false; + cbPINDBSpecific.Checked = false; + } + } + } +} diff --git a/PluginTools.cs b/PluginTools.cs new file mode 100644 index 0000000..3832747 --- /dev/null +++ b/PluginTools.cs @@ -0,0 +1,321 @@ +using KeePass.Forms; +using KeePass.UI; +using KeePassLib.Utility; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace PluginTools +{ + public static class Tools + { + private static Version m_KPVersion = null; + public static Version KeePassVersion { get { return m_KPVersion; } } + public static string DefaultCaption = string.Empty; + public static string PluginURL = string.Empty; + + static Tools() + { + KeePass.UI.GlobalWindowManager.WindowAdded += OnWindowAdded; + KeePass.UI.GlobalWindowManager.WindowRemoved += OnWindowRemoved; + m_KPVersion = typeof(KeePass.Program).Assembly.GetName().Version; + } + + #region Form and field handling + public static object GetField(string field, object obj) + { + BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; + return GetField(field, obj, bf); + } + + public static object GetField(string field, object obj, BindingFlags bf) + { + if (obj == null) return null; + FieldInfo fi = obj.GetType().GetField(field, bf); + if (fi == null) return null; + return fi.GetValue(obj); + } + + public static Control GetControl(string control) + { + return GetControl(control, KeePass.Program.MainForm); + } + + public static Control GetControl(string control, Control form) + { + if (form == null) return null; + if (string.IsNullOrEmpty(control)) return null; + Control[] cntrls = form.Controls.Find(control, true); + if (cntrls.Length == 0) return null; + return cntrls[0]; + } + #endregion + + #region Plugin options and instance + public static object GetPluginInstance(string PluginName) + { + string comp = PluginName + "." + PluginName + "Ext"; + BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; + try + { + var PluginManager = GetField("m_pluginManager", KeePass.Program.MainForm); + var PluginList = GetField("m_vPlugins", PluginManager); + MethodInfo IteratorMethod = PluginList.GetType().GetMethod("System.Collections.Generic.IEnumerable.GetEnumerator", bf); + IEnumerator PluginIterator = (IEnumerator)(IteratorMethod.Invoke(PluginList, null)); + while (PluginIterator.MoveNext()) + { + object result = GetField("m_pluginInterface", PluginIterator.Current); + if (comp == result.GetType().ToString()) return result; + } + } + + catch (Exception) { } + return null; + } + + public static event EventHandler OptionsFormShown; + public static event EventHandler OptionsFormClosed; + + private static bool OptionsEnabled = (KeePass.Program.Config.UI.UIFlags & (ulong)KeePass.App.Configuration.AceUIFlags.DisableOptions) != (ulong)KeePass.App.Configuration.AceUIFlags.DisableOptions; + private static bool m_ActivatePluginTab = false; + private static OptionsForm m_of = null; + private const string c_tabRookiestyle = "m_tabRookiestyle"; + private const string c_tabControlRookiestyle = "m_tabControlRookiestyle"; + private static string m_TabPageName = string.Empty; + private static bool m_OptionsShown = false; + private static bool m_PluginContainerShown = false; + + public static void AddPluginToOptionsForm(KeePass.Plugins.Plugin p, UserControl uc) + { + m_OptionsShown = m_PluginContainerShown = false; + TabPage tPlugin = new TabPage(DefaultCaption); + tPlugin.CreateControl(); + tPlugin.Name = m_TabPageName = c_tabRookiestyle + p.GetType().Name; + uc.Dock = DockStyle.Fill; + uc.Padding = new Padding(15, 10, 15, 10); + tPlugin.Controls.Add(uc); + TabControl tcPlugins = AddPluginTabContainer(); + int i = 0; + bool insert = false; + for (int j = 0; j < tcPlugins.TabPages.Count; j++) + { + if (string.Compare(tPlugin.Text, tcPlugins.TabPages[j].Text, StringComparison.CurrentCultureIgnoreCase) < 0) + { + i = j; + insert = true; + break; + } + } + if (!insert) i = tcPlugins.TabPages.Count; + tcPlugins.TabPages.Insert(i, tPlugin); + if (p.SmallIcon != null) + { + tcPlugins.ImageList.Images.Add(tPlugin.Name, p.SmallIcon); + tPlugin.ImageKey = tPlugin.Name; + } + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + if (m_ActivatePluginTab) + { + tcMain.SelectedIndex = tcMain.TabPages.IndexOfKey(c_tabRookiestyle); + KeePass.Program.Config.Defaults.OptionsTabIndex = (uint)tcMain.SelectedIndex; + tcPlugins.SelectedIndex = tcPlugins.TabPages.IndexOf(tPlugin); + } + m_ActivatePluginTab = false; + if (!string.IsNullOrEmpty(PluginURL)) + AddPluginLink(uc); + } + + private static void OnPluginTabsSelected(object sender, TabControlEventArgs e) + { + m_OptionsShown |= (e.TabPage.Name == m_TabPageName); + m_PluginContainerShown |= (m_OptionsShown || (e.TabPage.Name == c_tabRookiestyle)); + } + + public static UserControl GetPluginFromOptions(KeePass.Plugins.Plugin p, out bool PluginOptionsShown) + { + PluginOptionsShown = m_OptionsShown && m_PluginContainerShown; + TabPage tPlugin = Tools.GetControl(c_tabRookiestyle + p.GetType().Name, m_of) as TabPage; + if (tPlugin == null) return null; + return tPlugin.Controls[0] as UserControl; + } + + public static void ShowOptions() + { + m_ActivatePluginTab = true; + if (OptionsEnabled) + KeePass.Program.MainForm.ToolsMenu.DropDownItems["m_menuToolsOptions"].PerformClick(); + else + { + m_of = new OptionsForm(); + m_of.InitEx(KeePass.Program.MainForm.ClientIcons); + m_of.ShowDialog(); + } + } + + private static void AddPluginLink(UserControl uc) + { + LinkLabel llUrl = new LinkLabel(); + llUrl.Links.Add(0, 0, PluginURL); + llUrl.Text = PluginURL; + uc.Controls.Add(llUrl); + llUrl.Dock = DockStyle.Bottom; + llUrl.LinkClicked += new LinkLabelLinkClickedEventHandler(PluginURLClicked); + } + + private static void PluginURLClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + string target = e.Link.LinkData as string; + System.Diagnostics.Process.Start(target); + } + + private static void OnOptionsFormShown(object sender, EventArgs e) + { + m_of.Shown -= OnOptionsFormShown; + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + if (!tcMain.TabPages.ContainsKey(c_tabRookiestyle)) return; + TabPage tPlugins = tcMain.TabPages[c_tabRookiestyle]; + TabControl tcPlugins = Tools.GetControl(c_tabControlRookiestyle, tPlugins) as TabControl; + tcMain.Selected += OnPluginTabsSelected; + tcPlugins.Selected += OnPluginTabsSelected; + tcMain.ImageList.Images.Add(c_tabRookiestyle + "Icon", (Image)KeePass.Program.Resources.GetObject("B16x16_BlockDevice")); + tPlugins.ImageKey = c_tabRookiestyle + "Icon"; + m_PluginContainerShown |= tcMain.SelectedTab == tPlugins; + m_OptionsShown |= (tcPlugins.SelectedTab.Name == m_TabPageName); + } + + private static void OnWindowAdded(object sender, KeePass.UI.GwmWindowEventArgs e) + { + if (OptionsFormShown == null) return; + if (e.Form is OptionsForm) + { + m_of = e.Form as OptionsForm; + m_of.Shown += OnOptionsFormShown; + OptionsFormsEventArgs o = new OptionsFormsEventArgs(m_of); + OptionsFormShown(sender, o); + } + } + + private static void OnWindowRemoved(object sender, KeePass.UI.GwmWindowEventArgs e) + { + if (OptionsFormClosed == null) return; + if (e.Form is OptionsForm) + { + OptionsFormsEventArgs o = new OptionsFormsEventArgs(m_of); + OptionsFormClosed(sender, o); + } + } + + private static TabControl AddPluginTabContainer() + { + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + TabPage tPlugins = null; + TabControl tcPlugins = null; + if (tcMain.TabPages.ContainsKey(c_tabRookiestyle)) + { + tPlugins = tcMain.TabPages[c_tabRookiestyle]; + tcPlugins = (TabControl)tPlugins.Controls[c_tabControlRookiestyle]; + } + else + { + tPlugins = new TabPage(KeePass.Resources.KPRes.Plugin + " " + m_of.Text); + tPlugins.Name = c_tabRookiestyle; + tPlugins.CreateControl(); + if (!OptionsEnabled) + { + while (tcMain.TabCount > 0) + tcMain.TabPages.RemoveAt(0); + } + tcMain.TabPages.Add(tPlugins); + tcPlugins = new TabControl(); + tcPlugins.Name = c_tabControlRookiestyle; + tcPlugins.Dock = DockStyle.Fill; + tcPlugins.Multiline = true; + tcPlugins.CreateControl(); + if (tcPlugins.ImageList == null) + tcPlugins.ImageList = new ImageList(); + tPlugins.Controls.Add(tcPlugins); + } + return tcPlugins; + } + + public class OptionsFormsEventArgs : EventArgs + { + public Form form; + + public OptionsFormsEventArgs(Form form) + { + this.form = form; + } + } + #endregion + + #region MessageBox shortcuts + public static DialogResult ShowError(string msg) + { + return ShowError(msg, DefaultCaption); + } + + public static DialogResult ShowInfo(string msg) + { + return ShowInfo(msg, DefaultCaption); + } + + public static DialogResult AskYesNo(string msg) + { + return AskYesNo(msg, DefaultCaption); + } + + public static DialogResult ShowError(string msg, string caption) + { + return MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + public static DialogResult ShowInfo(string msg, string caption) + { + return MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public static DialogResult AskYesNo(string msg, string caption) + { + return MessageBox.Show(msg, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + } + #endregion + + #region GlobalWindowManager + public static void GlobalWindowManager(Form form) + { + if ((form == null) || (form.IsDisposed)) return; + form.Load += FormLoaded; + form.FormClosed += FormClosed; + } + + private static void FormLoaded(object sender, EventArgs e) + { + KeePass.UI.GlobalWindowManager.AddWindow(sender as Form); + } + + private static void FormClosed(object sender, FormClosedEventArgs e) + { + KeePass.UI.GlobalWindowManager.RemoveWindow(sender as Form); + } + #endregion + } + + public static class DPIAwareness + { + public static readonly Size Size16 = new Size(DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + + public static Image Scale16x16(Image img) + { + return Scale(img, 16, 16); + } + + public static Image Scale(Image img, int x, int y) + { + if (img == null) return null; + return GfxUtil.ScaleImage(img, DpiUtil.ScaleIntX(x), DpiUtil.ScaleIntY(y)); + } + } +} \ No newline at end of file diff --git a/PluginTranslation.cs b/PluginTranslation.cs new file mode 100644 index 0000000..d14b7b4 --- /dev/null +++ b/PluginTranslation.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; +using System.IO; + +using KeePass.Plugins; +using KeePass.Util; +using KeePassLib.Utility; + +namespace PluginTranslation +{ + public static class PluginTranslate + { + #region Definitions of translated texts go here + public const string PluginName = "LockAssist"; + public static string FirstTimeInfo + { + get { return TryGet("FirstTimeInfo", @"Quick Unlock offers two operation modes. +Please choose your preferred way of working."); } + } + public static string OptionsQUMode + { + get { return TryGet("OptionsQUMode", @"Mode:"); } + } + public static string Active + { + get { return TryGet("Active", @"Enable Quick Unlock"); } + } + public static string OptionsSLMinimize + { + get { return TryGet("OptionsSLMinimize", @"Softlock on minimize"); } + } + public static string KeyProvNoQuickUnlock + { + get { return TryGet("KeyProvNoQuickUnlock", @"No Quick Unlock key found. Quick Unlock is not possible."); } + } + public static string OptionsNotSavedSoftlock + { + get { return TryGet("OptionsNotSavedSoftlock", @"Softlock active. Settings have NOT been saved."); } + } + public static string OptionsQUReqInfoDB + { + get { return TryGet("OptionsQUReqInfoDB", @"Prerequsites for mode 'database password': +- Database masterkey contains a password +- Option 'Remember master password' is active + +Quick Unlock entry will be used as fallback"); } + } + public static string ButtonCancel + { + get { return TryGet("ButtonCancel", @"Cancel"); } + } + public static string OptionsQUSettings + { + get { return TryGet("OptionsQUSettings", @"Quick Unlock"); } + } + public static string OptionsQUModeEntry + { + get { return TryGet("OptionsQUModeEntry", @"Quick Unlock entry only"); } + } + public static string OptionsQUModeDatabasePW + { + get { return TryGet("OptionsQUModeDatabasePW", @"Database password"); } + } + public static string OptionsSLInfo + { + get { return TryGet("OptionsSLInfo", @"Softlock hides following sensitive information while still allowing Auto-Type as well as other integration: +- group list +- entry list +- entry view +- all forms NOT mentioned in config file property QuickUnlock.SoftLock.ExcludeForms + +Valid Quick Unlock settings required, Quick Unlock does NOT need to be active"); } + } + public static string OptionsQUEntryCreated + { + get { return TryGet("OptionsQUEntryCreated", @"Quick Unlock entry created. Please edit and set Quick Unlock PIN as password"); } + } + public static string OptionsSLSettings + { + get { return TryGet("OptionsSLSettings", @"Softlock"); } + } + public static string OptionsQUSettingsPerDB + { + get { return TryGet("OptionsQUSettingsPerDB", @"Settings are DB specific"); } + } + public static string SoftlockModeUnhide + { + get { return TryGet("SoftlockModeUnhide", @"Softlock active. Click to deactivate"); } + } + public static string OptionsSwitchDBToGeneral + { + get { return TryGet("OptionsSwitchDBToGeneral", @"Database specific settings switched off. +Revert back to general settings? +'No' will set current settings as global settings"); } + } + public static string OptionsQUPINLength + { + get { return TryGet("OptionsQUPINLength", @"PIN length:"); } + } + public static string ButtonUnlock + { + get { return TryGet("ButtonUnlock", @"Unlock"); } + } + public static string UnlockLabel + { + get { return TryGet("UnlockLabel", @"Quick Unlock PIN:"); } + } + public static string OptionsQUEntryCreate + { + get { return TryGet("OptionsQUEntryCreate", @"Quick Unlock entry could not be found. Create it now?"); } + } + public static string KeyProvNoCreate + { + get { return TryGet("KeyProvNoCreate", @"This key provider cannot be used to create keys."); } + } + public static string OptionsSLSecondsToActivate + { + get { return TryGet("OptionsSLSecondsToActivate", @"Softlock after inactivity (seconds):"); } + } + public static string ButtonOK + { + get { return TryGet("ButtonOK", @"OK"); } + } + public static string OptionsQUInfoRememberPassword + { + get { return TryGet("OptionsQUInfoRememberPassword", @"'Remember master password' needs to be active in Options -> Security. +Please don't forget to activate"); } + } + public static string OptionsQUUseLast + { + get { return TryGet("OptionsQUUseLast", @"Use last {0} characters as PIN"); } + } + public static string OptionsQUUseFirst + { + get { return TryGet("OptionsQUUseFirst", @"Use first {0} characters as PIN"); } + } + public static string SoftlockModeUnhideForms + { + get { return TryGet("SoftlockModeUnhideForms", @"Softlock active. Click topmost form to deactivate"); } + } + public static string WrongPIN + { + get { return TryGet("WrongPIN", @"The entered PIN was not correct. +Database stays locked and can only be unlocked with the original masterkey"); } + } + public static string OptionsLockWorkspace + { + get { return TryGet("OptionsLockWorkspace", @"Global '{0} / {1}'"); } + } + public static string OptionsLockWorkspaceDesc + { + get { return TryGet("OptionsLockWorkspaceDesc", @"This option changes the behaviour of 'Lock Workspace' for both the menu entry as well as the toolbar button. + +If it's active ALL loaded databases are locked / unlocked by using these commands. +In this case it depends on the active document's state whether a global lock or global unlock is performed. + +If the [Shift] key is pressed while using these commands only the active document is processed."); } + } + #endregion + + #region NO changes in this area + private static bool Debug = KeePass.Program.CommandLineArgs[KeePass.App.AppDefs.CommandLineOptions.Debug] != null; + private static StringDictionary m_translation = new StringDictionary(); + + public static void Init(Plugin plugin, string LanguageCodeIso6391) + { + try + { + string filename = GetFilename(plugin.GetType().Namespace, LanguageCodeIso6391); + + string translation = File.ReadAllText(filename); + + XmlSerializer xs = new XmlSerializer(m_translation.GetType()); + m_translation = (StringDictionary)xs.Deserialize(new StringReader(translation)); + } + catch (Exception) { } + } + + public static string TryGet(string key, string def) + { + string result = string.Empty; + if (m_translation.TryGetValue(key, out result)) + return result; + else + return def; + } + + private static string GetFilename(string plugin, string lang) + { + string filename = UrlUtil.GetFileDirectory(WinUtil.GetExecutable(), true, true); + filename += KeePass.App.AppDefs.PluginsDir + UrlUtil.LocalDirSepChar + "Translations" + UrlUtil.LocalDirSepChar; + filename += plugin + "." + lang + ".language.xml"; + return filename; + } + #endregion + } + + #region NO changes in this area + [XmlRoot("Translation")] + public class StringDictionary : Dictionary, IXmlSerializable + { + public System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + bool wasEmpty = reader.IsEmptyElement; + reader.Read(); + if (wasEmpty) return; + while (reader.NodeType != XmlNodeType.EndElement) + { + reader.ReadStartElement("item"); + reader.ReadStartElement("key"); + string key = reader.ReadContentAsString(); + reader.ReadEndElement(); + reader.ReadStartElement("value"); + string value = reader.ReadContentAsString(); + reader.ReadEndElement(); + this.Add(key, value); + reader.ReadEndElement(); + reader.MoveToContent(); + } + reader.ReadEndElement(); + } + + public void WriteXml(XmlWriter writer) + { + foreach (string key in this.Keys) + { + writer.WriteStartElement("item"); + writer.WriteStartElement("key"); + writer.WriteString(key); + writer.WriteEndElement(); + writer.WriteStartElement("value"); + writer.WriteString(this[key]); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + } + } + #endregion +} \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e091b8b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("LockAssist")] +[assembly: AssemblyDescription("Adds a quick unlock option as key provider and hides KeePass screens after defined inactivity")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("rookiestyle")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(true)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("4712d887-6685-4cb1-b67b-e981119fe3d2")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.3")] +[assembly: AssemblyFileVersion("0.3")] diff --git a/QuickUnlock.cs b/QuickUnlock.cs new file mode 100644 index 0000000..6ef5f1d --- /dev/null +++ b/QuickUnlock.cs @@ -0,0 +1,220 @@ +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Keys; +using KeePassLib.Security; +using KeePassLib.Serialization; +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public partial class LockAssistExt : Plugin, IMessageFilter + { + private void QuickUnlock_Init() + { + m_kp = new QuickUnlockKeyProv(); + m_host.KeyProviderPool.Add(m_kp); + m_host.MainWindow.FileClosingPre += OnFileClosePre; + m_host.MainWindow.FileOpened += OnFileOpened; + GlobalWindowManager.WindowAdded += OnWindowAdded; + } + + #region Eventhandler for opening and closing a DB + private void OnFileOpened(object sender, FileOpenedEventArgs e) + { + CheckSoftlockMode(); + m_options.ReadConfig(e.Database); + if (m_options.FirstTime && + (!m_options.UsePassword && (GetQuickUnlockEntry(e.Database) == null) + || (m_options.UsePassword && !Program.Config.Security.MasterPassword.RememberWhileOpen))) + { + Tools.ShowInfo(PluginTranslate.FirstTimeInfo); + Tools.ShowOptions(); + m_host.CustomConfig.SetBool("LockAssist.FirstTime", false); + } + if (!m_options.QUActive) return; + //Restore previously stored information about the masterkey + QuickUnlockOldKeyInfo ok = m_kp.GetOldKey(e.Database); + if (ok == null) return; + KcpCustomKey ck = (KcpCustomKey)e.Database.MasterKey.GetUserKey(typeof(KcpCustomKey)); + if ((ck == null) || (ck.Name != QuickUnlockKeyProv.KeyProviderName)) return; + + e.Database.MasterKey.RemoveUserKey(ck); + if (ok.pwHash != null) + { + KcpPassword p = m_kp.DeserializePassword(ok.pwHash, Program.Config.Security.MasterPassword.RememberWhileOpen); + e.Database.MasterKey.AddUserKey(p); + } + if (!string.IsNullOrEmpty(ok.keyFile)) e.Database.MasterKey.AddUserKey(new KcpKeyFile(ok.keyFile)); + if (ok.account) e.Database.MasterKey.AddUserKey(new KcpUserAccount()); + Program.Config.Defaults.SetKeySources(e.Database.IOConnectionInfo, e.Database.MasterKey); + } + + private void OnFileClosePre(object sender, FileClosingEventArgs e) + { + //Do quick unlock only in case of locking + //Do NOT do quick unlock in case of closing the database + if (e.Flags != FileEventFlags.Locking) return; + m_options.ReadConfig(e.Database); + if (!m_options.QUActive) return; + ProtectedString QuickUnlockKey = null; + if (Program.Config.Security.MasterPassword.RememberWhileOpen && m_options.UsePassword) + QuickUnlockKey = GetQuickUnlockKeyFromMasterKey(e.Database); + if (QuickUnlockKey == null) + QuickUnlockKey = GetQuickUnlockKeyFromEntry(e.Database); + QuickUnlockKey = TrimQuickUnlockKey(QuickUnlockKey); + if ((QuickUnlockKey == null) || QuickUnlockKey.IsEmpty) return; + m_kp.AddDb(e.Database, QuickUnlockKey, Program.Config.Security.MasterPassword.RememberWhileOpen && m_options.UsePassword); + } + #endregion + + #region Unlock / KeyPromptForm + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + if (!(e.Form is KeyPromptForm) && !(e.Form is KeyCreationForm)) return; + e.Form.Shown += (o, x) => OnKeyFormShown(o, false); + } + + public static void OnKeyFormShown(object sender, bool resetFile) + { + if (CheckGlobalUnlock()) + { + GlobalWindowManager.RemoveWindow(sender as Form); + (sender as Form).Close();(sender as Form).Dispose(); + GlobalUnlock(sender, null); + return; + } + Form keyform = (sender as Form); + try + { + ComboBox cmbKeyFile = (ComboBox)Tools.GetControl("m_cmbKeyFile", keyform); + if (cmbKeyFile == null) return; + int index = cmbKeyFile.Items.IndexOf(QuickUnlockKeyProv.KeyProviderName); + //Quick Unlock cannot be used to create a key ==> Remove it from list of key providers + if (keyform is KeyCreationForm) + { + if (index == -1) return; + cmbKeyFile.Items.RemoveAt(index); + List keyfiles = (List)Tools.GetField("m_lKeyFileNames", keyform); + if (keyfiles != null) keyfiles.Remove(QuickUnlockKeyProv.KeyProviderName); + return; + } + + //Key prompt form is shown + IOConnectionInfo dbIOInfo = (IOConnectionInfo)Tools.GetField("m_ioInfo", keyform); + if (m_bContinueUnlock) + { + CheckBox cbContinueUnlock = new CheckBox(); + cbContinueUnlock.AutoSize = true; + cbContinueUnlock.Text = KeePass.Resources.KPRes.LockMenuUnlock; + cbContinueUnlock.Checked = true; + cbContinueUnlock.Name = c_LockAssistContinueUnlockWorkbench; + cbContinueUnlock.CheckedChanged += (o, e) => { m_bContinueUnlock = cbContinueUnlock.Checked; }; + cbContinueUnlock.Checked = cbContinueUnlock.Checked; + CheckBox cbPassword = (CheckBox)Tools.GetControl("m_cbPassword", keyform); + CheckBox cbAccount = (CheckBox)Tools.GetControl("m_cbUserAccount", keyform); + cbContinueUnlock.Left = cbAccount.Left; + cbContinueUnlock.Top = cbAccount.Top + 30; + cbAccount.Parent.Controls.Add(cbContinueUnlock); + } + //If Quick Unlock is possible show the Quick Unlock form + if ((index != -1) && (dbIOInfo != null) && QuickUnlockKeyProv.HasDB(dbIOInfo.Path)) + { + cmbKeyFile.SelectedIndex = index; + CheckBox cbPassword = (CheckBox)Tools.GetControl("m_cbPassword", keyform); + CheckBox cbAccount = (CheckBox)Tools.GetControl("m_cbUserAccount", keyform); + Button bOK = (Button)Tools.GetControl("m_btnOK", keyform); + if ((bOK != null) && (cbPassword != null) && (cbAccount != null)) + { + UIUtil.SetChecked(cbPassword, false); + UIUtil.SetChecked(cbAccount, false); + bOK.PerformClick(); + } + return; + } + + //Quick Unlock is not possible => Remove it from list of key providers + if ((resetFile || ((dbIOInfo != null) && !QuickUnlockKeyProv.HasDB(dbIOInfo.Path))) && (index != -1)) + { + cmbKeyFile.Items.RemoveAt(index); + List keyfiles = (List)Tools.GetField("m_lKeyFileNames", keyform); + if (keyfiles != null) keyfiles.Remove(QuickUnlockKeyProv.KeyProviderName); + if (resetFile) cmbKeyFile.SelectedIndex = 0; + } + } + catch (Exception) { } + } + #endregion + + #region QuickUnlockKey handling + private ProtectedString GetQuickUnlockKeyFromMasterKey(PwDatabase db) + { + /* + * Try to create QuickUnlockKey based on password + * + * If no password is contained in MasterKey there + * EITHER is no password at all + * OR the database was unlocked with Quick Unlock + * In these case ask our key provider for the original password + */ + ProtectedString QuickUnlockKey = null; + try + { + KcpPassword pw = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword)); + if (pw != null) + QuickUnlockKey = pw.Password; + } + catch (Exception) { } + if ((QuickUnlockKey != null) && (QuickUnlockKey.Length > 0)) return QuickUnlockKey; + return null; + } + + private ProtectedString GetQuickUnlockKeyFromEntry(PwDatabase db) + { + PwEntry QuickUnlockEntry = GetQuickUnlockEntry(db); + if (QuickUnlockEntry == null) return null; + return QuickUnlockEntry.Strings.GetSafe(PwDefs.PasswordField); + } + + public static PwEntry GetQuickUnlockEntry(PwDatabase db) + { + if ((db == null) || !db.IsOpen) return null; + SearchParameters sp = new SearchParameters(); + sp.SearchInTitles = true; + sp.ExcludeExpired = true; + sp.SearchString = QuickUnlockKeyProv.KeyProviderName; + PwObjectList entries = new PwObjectList(); + db.RootGroup.SearchEntries(sp, entries); + if ((entries == null) || (entries.UCount == 0)) return null; + return entries.GetAt(0); + } + + private ProtectedString TrimQuickUnlockKey(ProtectedString QuickUnlockKey) + { + if ((QuickUnlockKey == null) || (QuickUnlockKey.Length <= m_options.Length)) return QuickUnlockKey; + int startIndex = 0; + if (!m_options.FromEnd) + startIndex = m_options.Length; + QuickUnlockKey = QuickUnlockKey.Remove(startIndex, QuickUnlockKey.Length - m_options.Length); + return QuickUnlockKey; + } + #endregion + + private void QuickUnlock_Terminate() + { + m_host.KeyProviderPool.Remove(m_kp); + m_kp = null; + m_host.MainWindow.FileClosingPre -= OnFileClosePre; + m_host.MainWindow.FileOpened -= OnFileOpened; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + } + } +} diff --git a/QuickUnlockKeyProv.cs b/QuickUnlockKeyProv.cs new file mode 100644 index 0000000..0fe7644 --- /dev/null +++ b/QuickUnlockKeyProv.cs @@ -0,0 +1,231 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Security.Cryptography; + +using KeePassLib.Keys; +using KeePassLib; +using KeePassLib.Cryptography; +using KeePassLib.Security; +using KeePassLib.Utility; +using KeePassLib.Cryptography.Cipher; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public class QuickUnlockOldKeyInfo + { + public ProtectedString QuickUnlockKey = ProtectedString.EmptyEx; + public ProtectedBinary pwHash = null; + public string keyFile = string.Empty; + public bool account = false; + public ProtectedBinary PINCheck = null; + } + + public class QuickUnlockKeyProv : KeyProvider + { + public static string KeyProviderName = PluginTranslate.PluginName + " - Quick Unlock"; + private static byte[] m_PINCheck = StrUtil.Utf8.GetBytes(KeyProviderName); + public override string Name { get { return KeyProviderName; } } + public override bool SecureDesktopCompatible { get { return true; } } + public override bool Exclusive { get { return true; } } + public override bool DirectKey { get { return true; } } + public override bool GetKeyMightShowGui { get { return true; } } + + private static Dictionary m_hashedKey = new Dictionary(); + private static Dictionary m_originalKey = new Dictionary(); + + public override byte[] GetKey(KeyProviderQueryContext ctx) + { + if (ctx.CreatingNewKey) //should not happen but you never know + { + Tools.ShowError(PluginTranslate.KeyProvNoCreate); + return null; + } + ProtectedBinary encryptedKey = new ProtectedBinary(); + if (!m_hashedKey.TryGetValue(ctx.DatabasePath, out encryptedKey)) + { + Tools.ShowError(PluginTranslate.KeyProvNoQuickUnlock); + return null; + } + + var uForm = new UnlockForm(); + if (uForm.ShowDialog() != DialogResult.OK) return null; + ProtectedString QuickUnlockKey = uForm.GetQuickUnlockKey(); + uForm.Close(); + + m_hashedKey.Remove(ctx.DatabasePath); + if (KeePass.UI.GlobalWindowManager.TopWindow is KeePass.Forms.KeyPromptForm) + { + QuickUnlockOldKeyInfo ok = null; + if (m_originalKey.TryGetValue(ctx.DatabasePath, out ok)) + { + byte[] comparePIN = DecryptKey(QuickUnlockKey, ok.PINCheck).ReadData(); + if (StrUtil.Utf8.GetString(comparePIN) != KeyProviderName) + { + LockAssistExt.OnKeyFormShown(KeePass.UI.GlobalWindowManager.TopWindow, true); + Tools.ShowError(PluginTranslate.WrongPIN); + return null; + } + } + } + return DecryptKey(QuickUnlockKey, encryptedKey).ReadData(); + } + + public QuickUnlockOldKeyInfo GetOldKey(PwDatabase db) + { + QuickUnlockOldKeyInfo ok = new QuickUnlockOldKeyInfo(); + if (m_originalKey.TryGetValue(db.IOConnectionInfo.Path, out ok)) + { + if ((ok.pwHash != null) && (ok.pwHash.Length != 0)) + ok.pwHash = DecryptKey(ok.QuickUnlockKey, ok.pwHash); + m_originalKey.Remove(db.IOConnectionInfo.Path); + return ok; + } + return null; + } + + public void AddDb(PwDatabase db, ProtectedString QuickUnlockKey, bool savePw) + { + RemoveDb(db); + ProtectedBinary pbKey = CreateMasterKeyHash(db.MasterKey); + m_hashedKey.Add(db.IOConnectionInfo.Path, EncryptKey(QuickUnlockKey, pbKey)); + AddOldMasterKey(db, QuickUnlockKey, savePw); + } + + private void AddOldMasterKey(PwDatabase db, ProtectedString QuickUnlockKey, bool savePw) + { + QuickUnlockOldKeyInfo ok = new QuickUnlockOldKeyInfo(); + ok.QuickUnlockKey = QuickUnlockKey; + if (db.MasterKey.ContainsType(typeof(KcpPassword))) + ok.pwHash = EncryptKey(QuickUnlockKey, SerializePassword(db.MasterKey.GetUserKey(typeof(KcpPassword)) as KcpPassword, savePw)); + if (db.MasterKey.ContainsType(typeof(KcpKeyFile))) + ok.keyFile = (db.MasterKey.GetUserKey(typeof(KcpKeyFile)) as KcpKeyFile).Path; + ok.account = db.MasterKey.ContainsType(typeof(KcpUserAccount)); + ok.PINCheck = EncryptKey(QuickUnlockKey, new ProtectedBinary(true, m_PINCheck)); + m_originalKey.Add(db.IOConnectionInfo.Path, ok); + } + + public static bool HasDB(string db) + { + if (m_hashedKey.ContainsKey(db)) return true; + m_originalKey.Remove(db); + return false; + } + + private void RemoveDb(PwDatabase db) + { + m_hashedKey.Remove(db.IOConnectionInfo.Path); + m_originalKey.Remove(db.IOConnectionInfo.Path); + } + + private ProtectedBinary CreateMasterKeyHash(CompositeKey mk) + { + List keys = new List(); + int keysLength = 0; + foreach (var key in mk.UserKeys) //Hopefully we never need to consider the sequence... + { + ProtectedBinary pb = key.KeyData; + if (pb != null) + { + var pbArray = pb.ReadData(); + keys.Add(pbArray); + keysLength += pbArray.Length; + } + } + + byte[] allKeys = new byte[keysLength]; + int index = 0; + foreach (byte[] key in keys) + { + Array.Copy(key, 0, allKeys, index, key.Length); + index += key.Length; + MemUtil.ZeroByteArray(key); + } + + var result = new ProtectedBinary(true, allKeys); + MemUtil.ZeroByteArray(allKeys); + return result; + } + + private ProtectedBinary SerializePassword(KcpPassword p, bool savePassword) + { + //returned array always contains password hash + //password is contained only if requested + //check for p.Password != null as the user might disable Program.Config.Security.MasterPassword.RememberWhileOpen anytime + if (savePassword && (p.Password != null) && !p.Password.IsEmpty) + { + byte[] result = new byte[p.KeyData.Length + p.Password.ReadUtf8().Length]; + Array.Copy(p.KeyData.ReadData(), result, p.KeyData.Length); + Array.Copy(p.Password.ReadUtf8(), 0, result, p.KeyData.Length, p.Password.ReadUtf8().Length); + return new ProtectedBinary(true, result); + } + return p.KeyData; + } + + public KcpPassword DeserializePassword(ProtectedBinary serialized, bool setPassword) + { + //if password is stored and should be retrieved + //simply create a new instance + //instead of messing around with private members + KcpPassword p = new KcpPassword(string.Empty); + if (setPassword && (serialized.Length > p.KeyData.Length)) + { + byte[] pw = new byte[serialized.Length - p.KeyData.Length]; + Array.Copy(serialized.ReadData(), p.KeyData.Length, pw, 0, pw.Length); + ProtectedString pws = new ProtectedString(true, pw); + return new KcpPassword(pws.ReadString()); + } + + if (serialized.Length != p.KeyData.Length) + return null; + ProtectedBinary pb = new ProtectedBinary(true, serialized.ReadData()); + MemUtil.ZeroByteArray(serialized.ReadData()); + typeof(KcpPassword).GetField("m_pbKeyData", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(p, pb); + typeof(KcpPassword).GetField("m_psPassword", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(p, null); + return p; + } + + private ProtectedBinary EncryptKey(ProtectedString QuickUnlockKey, ProtectedBinary pbKey) + { + byte[] iv = CryptoRandom.Instance.GetRandomBytes(12); + ChaCha20Cipher cipher = new ChaCha20Cipher(AdjustQuickUnlockKey(QuickUnlockKey), iv); + + byte[] bKey = pbKey.ReadData(); + cipher.Encrypt(bKey, 0, bKey.Length); + + byte[] result = new byte[iv.Length + bKey.Length]; + iv.CopyTo(result, 0); + bKey.CopyTo(result, iv.Length); + + return new ProtectedBinary(true, result); + } + + private ProtectedBinary DecryptKey(ProtectedString QuickUnlockKey, ProtectedBinary pbCrypted) + { + byte[] crypted = pbCrypted.ReadData(); + byte[] iv = new byte[12]; + Array.Copy(crypted, iv, iv.Length); + + byte[] cryptedKey = new byte[crypted.Length - iv.Length]; + Array.Copy(crypted, iv.Length, cryptedKey, 0, cryptedKey.Length); + + ChaCha20Cipher cipher = new ChaCha20Cipher(AdjustQuickUnlockKey(QuickUnlockKey), iv); + byte[] bDecrypted = pbCrypted.ReadData(); + cipher.Decrypt(cryptedKey, 0, cryptedKey.Length); + ProtectedBinary pbDecrypted = new ProtectedBinary(true, cryptedKey); + MemUtil.ZeroByteArray(cryptedKey); + return pbDecrypted; + } + + private byte[] AdjustQuickUnlockKey(ProtectedString QuickUnlockKey) + { + byte[] result = QuickUnlockKey.ReadUtf8(); + SHA256Managed sha = new SHA256Managed(); + return sha.ComputeHash(result); + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e9a16b --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# LockAssist +[![Version](https://img.shields.io/github/release/rookiestyle/lockassist)](https://github.com/rookiestyle/lockassist/releases/latest) +[![Releasedate](https://img.shields.io/github/release-date/rookiestyle/lockassist)](https://github.com/rookiestyle/lockassist/releases/latest) +[![Downloads](https://img.shields.io/github/downloads/rookiestyle/lockassist/total?color=%2300cc00)](https://github.com/rookiestyle/lockassist/releases/latest/download/lockassist.plgx)\ +[![License: GPL v3](https://img.shields.io/github/license/rookiestyle/lockassist)](https://www.gnu.org/licenses/gpl-3.0) + +LockAssist extends KeePass' lock/unlock mechanism and currently offers a Quick Unlock option. +Additional features will follow soon. + +# Table of Contents +- [Configuration](#configuration) +- [Usage](#usage) +- [Translations](#translations) +- [Download and Requirements](#download-and-requirements) + +# Configuration +LockAssist integrates into KeePass' options form.\ +Options + +# Usage +You'll find more details in the [Wiki](https://github.com/rookiestyle/lockassist/wiki) + +## Quick Unlock +Quick Unlock lets you unlock a previously opened database without the need to enter the complete password. +You can unlock your database with a only a few characters instead - your quick unlock key. +Have a look at the [Wiki](https://github.com/rookiestyle/lockassist/wiki/quick-unlock) to learn why this does not impose security risks. + +Options + +# Translations +LockAssist is provided with english language built-in and allows usage of translation files. +These translation files need to be placed in a folder called *Translations* inside in your plugin folder. +If a text is missing in the translation file, it is backfilled with the english text. +You're welcome to add additional translation files by creating a pull request. + +Naming convention for translation files: `lockassist..language.xml`\ +Example: `lockassist.de.language.xml` + +The language identifier in the filename must match the language identifier inside the KeePass language that you can select using *View -> Change language...*\ +This identifier is shown there as well, if you have [EarlyUpdateCheck](https://github.com/rookiestyle/earlyupdatecheck) installed + +# Download and Requirements +## Download +Please follow these links to download the plugin file itself. +- [Download newest release](https://github.com/rookiestyle/lockassist/releases/latest/download/LockAssist.plgx) +- [Download history](https://github.com/rookiestyle/lockassist/releases) + +If you're interested in any of the available translations in addition, please download them from the [Translations](Translations) folder. +## Requirements +* KeePass: 2.41 + diff --git a/Translations/LockAssist.de.language.xml b/Translations/LockAssist.de.language.xml new file mode 100644 index 0000000..0bf6118 --- /dev/null +++ b/Translations/LockAssist.de.language.xml @@ -0,0 +1,111 @@ + + + + 1 + + FirstTimeInfo + Quick Unlock bietet verschiedene Konfigurationsmöglichkeiten. +Bitte wähle eine der Möglichkeiten aus. + + + OptionsQUMode + Modus: + + + Active + Quick Unlock aktiv + + + KeyProvNoQuickUnlock + Keine Quick Unlock Daten vorhanden, + +Quick Unlock nicht möglich. + + + OptionsQUReqInfoDB + Voraussetzungen für Modus 'Datenbankpasswort': +- Hauptschlüssel der Datenbank enthält ein Passwort +- Option 'Hauptpasswort einer Datenbank merken' ist aktiv + +Ein vorhandener Quick Unlock Eintrag wird als Fallback genutzt. + + + OptionsQUSettings + Quick Unlock + + + OptionsQUModeEntry + NUR Quick Unlock Eintrag + + + OptionsQUModeDatabasePW + Datenbankpasswort + + + OptionsQUEntryCreated + Quick Unlock Eintrag erstellt. + +Bitte bearbeiten und Quick Unlock PIN als Password setzen. + + + OptionsQUSettingsPerDB + Konfiguration nur für aktive Datenbank + + + OptionsSwitchDBToGeneral + Datenbankspezifische Konfiguration deaktiviert. + +Klicke '{0}', um für diese Datenbank die globalen Einstellungen zu nutzen. +Klicke '{1}', um die Einstellungen dieser Datenbank als neue globalen Einstellungen zu verwenden. + + + OptionsQUPINLength + PIN Länge: + + + ButtonUnlock + Entsperren + + + UnlockLabel + Quick Unlock PIN: + + + OptionsQUEntryCreate + Quick Unlock Eintrag nicht gefunden. + +Anlegen? + + + KeyProvNoCreate + Dieser Schlüsselprovider kann nicht zum Erstellen von Hauptschlüsseln verwendet werden. + + + OptionsQUInfoRememberPassword + 'Hauptpasswort einer Datenbank merken' muss aktiv sein (Optionen -> Sicherheit) + +Bitte aktiviere diese Einstellung! + + + OptionsQUUseLast + Die letzten {0} Zeichen als PIN nutzen + + + OptionsQUUseFirst + Die ersten {0} Zeichen als PIN nutzen + + + WrongPIN + Die eingegebe PIN war nicht korrekt. + +Die Datenbank ist weiterhin gesperrt und kann nur mit dem vollständigen Hauptschlüssel entsperrt werden. + + + FirstTimeInfoRememberPassword + Quick Unlock bietet verschiedene Konfigurationsmöglichkeiten. +Bitte wähle eine der Möglichkeiten aus. + + \ No newline at end of file diff --git a/Translations/LockAssist.template.language.xml b/Translations/LockAssist.template.language.xml new file mode 100644 index 0000000..2ed3b7e --- /dev/null +++ b/Translations/LockAssist.template.language.xml @@ -0,0 +1,105 @@ + + + + 0 + + FirstTimeInfo + Quick Unlock offers two operation modes. +Please choose your preferred way of working. + + + OptionsQUMode + Mode: + + + Active + Quick Unlock active + + + KeyProvNoQuickUnlock + No Quick Unlock key found. + +Quick Unlock is not possible. + + + OptionsQUReqInfoDB + Prerequsites for mode 'database password': +- Database masterkey contains a password +- Option 'Remember master password' is active + +An existing Quick Unlock entry will be used as fallback + + + OptionsQUSettings + Quick Unlock + + + OptionsQUModeEntry + Quick Unlock entry only + + + OptionsQUModeDatabasePW + Database password + + + OptionsQUEntryCreated + Quick Unlock entry created. + +Please edit and set Quick Unlock PIN as password + + + OptionsQUSettingsPerDB + Settings are DB specific + + + OptionsSwitchDBToGeneral + Database specific settings switched off. + +Click '{0}' to use the global settings for this database. +Click '{1}' to make this database's settings the new global settings. + + + OptionsQUPINLength + PIN length: + + + ButtonUnlock + Unlock + + + UnlockLabel + Quick Unlock PIN: + + + OptionsQUEntryCreate + Quick Unlock entry could not be found. + +Create it now? + + + KeyProvNoCreate + This key provider cannot be used to create keys. + + + OptionsQUInfoRememberPassword + 'Remember master password' needs to be active in Options -> Security. +Please don't forget to activate this setting. + + + OptionsQUUseLast + Use last {0} characters as PIN + + + OptionsQUUseFirst + Use first {0} characters as PIN + + + WrongPIN + The entered PIN was not correct. + +The database stays locked and can only be unlocked with the original masterkey + + \ No newline at end of file diff --git a/UnlockForm.Designer.cs b/UnlockForm.Designer.cs new file mode 100644 index 0000000..d8fd0b7 --- /dev/null +++ b/UnlockForm.Designer.cs @@ -0,0 +1,135 @@ +using KeePass.UI; +namespace LockAssist +{ + partial class UnlockForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lLabel = new System.Windows.Forms.Label(); + this.bUnlock = new System.Windows.Forms.Button(); + this.bCancel = new System.Windows.Forms.Button(); + this.cbTogglePin = new System.Windows.Forms.CheckBox(); + this.stbPIN = new KeePass.UI.SecureTextBoxEx(); + this.cbContinueUnlock = new System.Windows.Forms.CheckBox(); + this.SuspendLayout(); + // + // lLabel + // + this.lLabel.AutoSize = true; + this.lLabel.Location = new System.Drawing.Point(42, 43); + this.lLabel.Name = "lLabel"; + this.lLabel.Size = new System.Drawing.Size(136, 20); + this.lLabel.TabIndex = 1; + this.lLabel.Text = "Quick Unlock PIN:"; + // + // bUnlock + // + this.bUnlock.DialogResult = System.Windows.Forms.DialogResult.OK; + this.bUnlock.Location = new System.Drawing.Point(195, 117); + this.bUnlock.Name = "bUnlock"; + this.bUnlock.Size = new System.Drawing.Size(100, 30); + this.bUnlock.TabIndex = 2; + this.bUnlock.Text = "Unlock"; + this.bUnlock.UseVisualStyleBackColor = true; + // + // bCancel + // + this.bCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.bCancel.Location = new System.Drawing.Point(301, 117); + this.bCancel.Name = "bCancel"; + this.bCancel.Size = new System.Drawing.Size(100, 30); + this.bCancel.TabIndex = 3; + this.bCancel.Text = "Cancel"; + this.bCancel.UseVisualStyleBackColor = true; + // + // cbTogglePin + // + this.cbTogglePin.Appearance = System.Windows.Forms.Appearance.Button; + this.cbTogglePin.AutoSize = true; + this.cbTogglePin.CheckAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cbTogglePin.Checked = true; + this.cbTogglePin.CheckState = System.Windows.Forms.CheckState.Checked; + this.cbTogglePin.Location = new System.Drawing.Point(364, 38); + this.cbTogglePin.Name = "cbTogglePin"; + this.cbTogglePin.Size = new System.Drawing.Size(37, 30); + this.cbTogglePin.TabIndex = 1; + this.cbTogglePin.Text = "***"; + this.cbTogglePin.UseVisualStyleBackColor = true; + this.cbTogglePin.CheckedChanged += new System.EventHandler(this.togglePIN_CheckedChanged); + // + // stbPIN + // + this.stbPIN.Location = new System.Drawing.Point(197, 40); + this.stbPIN.Name = "stbPIN"; + this.stbPIN.Size = new System.Drawing.Size(155, 26); + this.stbPIN.TabIndex = 0; + // + // cbContinueUnlock + // + this.cbContinueUnlock.AutoSize = true; + this.cbContinueUnlock.Location = new System.Drawing.Point(46, 78); + this.cbContinueUnlock.Name = "cbContinueUnlock"; + this.cbContinueUnlock.Size = new System.Drawing.Size(149, 24); + this.cbContinueUnlock.TabIndex = 4; + this.cbContinueUnlock.Text = "Continue unlock"; + this.cbContinueUnlock.UseVisualStyleBackColor = true; + this.cbContinueUnlock.CheckedChanged += new System.EventHandler(this.cbContinueUnlock_CheckedChanged); + // + // UnlockForm + // + this.AcceptButton = this.bUnlock; + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.bCancel; + this.ClientSize = new System.Drawing.Size(413, 187); + this.Controls.Add(this.cbContinueUnlock); + this.Controls.Add(this.cbTogglePin); + this.Controls.Add(this.bCancel); + this.Controls.Add(this.bUnlock); + this.Controls.Add(this.lLabel); + this.Controls.Add(this.stbPIN); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UnlockForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Quick Unlock"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private SecureTextBoxEx stbPIN; + private System.Windows.Forms.Label lLabel; + private System.Windows.Forms.Button bUnlock; + private System.Windows.Forms.Button bCancel; + private System.Windows.Forms.CheckBox cbTogglePin; + private System.Windows.Forms.CheckBox cbContinueUnlock; + } +} \ No newline at end of file diff --git a/UnlockForm.cs b/UnlockForm.cs new file mode 100644 index 0000000..c965e71 --- /dev/null +++ b/UnlockForm.cs @@ -0,0 +1,54 @@ +using System.Windows.Forms; +using System.Drawing; +using KeePassLib.Security; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public partial class UnlockForm : Form + { + public UnlockForm() + { + InitializeComponent(); + cbTogglePin.Image = (Image)KeePass.Program.Resources.GetObject("B19x07_3BlackDots"); + if (cbTogglePin.Image != null) + { + cbTogglePin.AutoSize = false; + cbTogglePin.Text = string.Empty; + if (KeePass.UI.UIUtil.IsDarkTheme) + cbTogglePin.Image = KeePass.UI.UIUtil.InvertImage(cbTogglePin.Image); + } + + Text = QuickUnlockKeyProv.KeyProviderName; + lLabel.Text = PluginTranslate.UnlockLabel; + bUnlock.Text = PluginTranslate.ButtonUnlock; + bCancel.Text = PluginTranslate.ButtonCancel; + + KeePass.UI.SecureTextBoxEx.InitEx(ref stbPIN); + cbTogglePin.Checked = true; + stbPIN.EnableProtection(cbTogglePin.Checked); + + cbContinueUnlock.Text = KeePass.Resources.KPRes.LockMenuUnlock; + cbContinueUnlock.Visible = cbContinueUnlock.Checked = LockAssistExt.m_bContinueUnlock; + } + + public ProtectedString GetQuickUnlockKey() + { + return stbPIN.TextEx; + } + + private void togglePIN_CheckedChanged(object sender, System.EventArgs e) + { + stbPIN.EnableProtection(cbTogglePin.Checked); + } + + private void cbContinueUnlock_CheckedChanged(object sender, System.EventArgs e) + { + CheckBox cbContinue = (CheckBox)Tools.GetControl(LockAssistExt.c_LockAssistContinueUnlockWorkbench, KeePass.UI.GlobalWindowManager.TopWindow); + if (cbContinue != null) + cbContinue.Checked = cbContinueUnlock.Checked; + } + } +} diff --git a/images/LockAssist - options.png b/images/LockAssist - options.png new file mode 100644 index 0000000..e085817 Binary files /dev/null and b/images/LockAssist - options.png differ diff --git a/images/LockAssist - quick unlock.png b/images/LockAssist - quick unlock.png new file mode 100644 index 0000000..3f42846 Binary files /dev/null and b/images/LockAssist - quick unlock.png differ diff --git a/plgxcreate.cmd b/plgxcreate.cmd new file mode 100644 index 0000000..01a045b --- /dev/null +++ b/plgxcreate.cmd @@ -0,0 +1,40 @@ +@echo off +set plgxnet=%1 +set plgxkp=%2 +set plgxos=%3 + +cls +cd %~dp0 + +for %%* in (.) do set CurrDirName=%%~nx* +echo Processing %CurrDirName% + +echo Deleting existing PlgX folder +rmdir /s /q plgx + +echo Creating PlgX folder +mkdir plgx + +echo Copying files +xcopy src plgx /s /e /exclude:plgxexclude.txt > nul + +echo Compiling PlgX +cd.. +cd _KeePass_Release +KeePass.exe --plgx-create "%~dp0plgx" -plgx-prereq-net:%plgxnet% -plgx-prereq-kp:%plgxkp% -plgx-prereq-os:%plgxos% +cd .. +cd %CurrDirName% + +echo Copying PlgX to KeePass plugin folder +copy plgx.plgx "..\_KeePass_Release\Plugins\%CurrDirName%.plgx" + +echo Releasing PlgX +move /y plgx.plgx "..\_Releases\%CurrDirName%.plgx" + +echo Cleaning up +rmdir /s /q plgx + +echo Compiled with following minimum requirements: +echo .NET = %plgxnet% +echo KeePass = %plgxkp% +echo OS = %plgxos% \ No newline at end of file diff --git a/plgxexclude.txt b/plgxexclude.txt new file mode 100644 index 0000000..94c84d5 --- /dev/null +++ b/plgxexclude.txt @@ -0,0 +1,8 @@ +bin\ +obj\ +.vs +.git +.user +.sln +.suo +.pdb \ No newline at end of file diff --git a/src/LockAssist.cs b/src/LockAssist.cs new file mode 100644 index 0000000..52c2b87 --- /dev/null +++ b/src/LockAssist.cs @@ -0,0 +1,101 @@ +using KeePass.Plugins; +using KeePassLib; +using System.Collections.Generic; +using System.Windows.Forms; + +using PluginTranslation; +using PluginTools; +using System.Drawing; +using System; + +namespace LockAssist +{ + public partial class LockAssistExt : Plugin + { + private static IPluginHost m_host = null; + private ToolStripMenuItem m_menu = null; + private static bool Terminated { get { return m_host == null; } } + + private QuickUnlock _qu = null; + + //private static LockAssistOptions m_options; + + public override bool Initialize(IPluginHost host) + { + if (m_host != null) Terminate(); + m_host = host; + + PluginTranslate.Init(this, KeePass.Program.Translation.Properties.Iso6391Code); + Tools.DefaultCaption = PluginTranslate.PluginName; + + m_menu = new ToolStripMenuItem(); + m_menu.Text = PluginTranslate.PluginName + "..."; + m_menu.Click += (o, e) => Tools.ShowOptions(); + m_menu.Image = m_host.MainWindow.ClientIcons.Images[(int)PwIcon.LockOpen]; + m_host.MainWindow.ToolsMenu.DropDownItems.Add(m_menu); + + Tools.OptionsFormShown += OptionsFormShown; + Tools.OptionsFormClosed += OptionsFormClosed; + + _qu = new QuickUnlock(); + + return true; + } + + public override void Terminate() + { + Tools.OptionsFormShown -= OptionsFormShown; + Tools.OptionsFormClosed -= OptionsFormClosed; + + _qu.Clear(); + _qu = null; + + m_host.MainWindow.ToolsMenu.DropDownItems.Remove(m_menu); + PluginDebug.SaveOrShow(); + m_host = null; + } + + #region Options + private void OptionsFormShown(object sender, Tools.OptionsFormsEventArgs e) + { + PluginDebug.AddInfo("Show options", 0); + OptionsForm options = new OptionsForm(); + options.InitEx(LockAssistConfig.GetOptions(m_host.Database)); + Tools.AddPluginToOptionsForm(this, options); + } + + private void OptionsFormClosed(object sender, Tools.OptionsFormsEventArgs e) + { + if (e.form.DialogResult != DialogResult.OK) return; + bool shown = false; + OptionsForm options = (OptionsForm)Tools.GetPluginFromOptions(this, out shown); + if (!shown) return; + var MyOptions = LockAssistConfig.GetOptions(m_host.Database); + LockAssistConfig NewOptions = options.GetOptions(); + bool changedConfig = MyOptions.ConfigChanged(NewOptions, false); + bool changedConfigTotal = MyOptions.ConfigChanged(NewOptions, true); + PluginDebug.AddInfo("Options form closed", 0, "Config changed: " + changedConfig.ToString(), "Config total changed:" + changedConfigTotal.ToString()); + + bool SwitchToNoDBSpecific = MyOptions.CopyFrom(NewOptions); + + if (SwitchToNoDBSpecific) + { + string sQuestion = string.Format(PluginTranslate.OptionsSwitchDBToGeneral, DialogResult.Yes.ToString(), DialogResult.No.ToString()); + if (Tools.AskYesNo(sQuestion) == DialogResult.No) + //Make current configuration the new global configuration + MyOptions.WriteConfig(null); + //Remove DB specific configuration + MyOptions.DeleteDBConfig(m_host.Database); + } + else MyOptions.WriteConfig(m_host.Database); + } +#endregion + + public override string UpdateUrl + { + get { return "https://raw.githubusercontent.com/rookiestyle/lockassist/master/version.info"; } + } + + public override Image SmallIcon { get { return m_menu.Image; } } + } +} \ No newline at end of file diff --git a/src/LockAssist.csproj b/src/LockAssist.csproj new file mode 100644 index 0000000..ff04158 --- /dev/null +++ b/src/LockAssist.csproj @@ -0,0 +1,93 @@ + + + + + 5 + + + 2.41 + + + + + Release + AnyCPU + {4712D887-6685-4CB1-B67B-E981119FE3D2} + Library + Properties + LockAssist + LockAssist + v3.5 + 512 + true + + + + true + full + false + ..\..\_KeePass_Debug\Plugins\ + TRACE;DEBUG + prompt + 4 + true + false + 5 + + + none + true + ..\..\_KeePass_Release\Plugins\ + + + prompt + 4 + false + 5 + + + + + + + + + + + + + Form + + + UnlockForm.cs + + + + + + UserControl + + + OptionsForm.cs + + + + + + + + + + {10938016-dee2-4a25-9a5a-8fd3444379ca} + KeePass + + + + + + + + + + + \ No newline at end of file diff --git a/src/LockAssistConfig.cs b/src/LockAssistConfig.cs new file mode 100644 index 0000000..c5967d0 --- /dev/null +++ b/src/LockAssistConfig.cs @@ -0,0 +1,128 @@ +using KeePassLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LockAssist +{ + internal class LockAssistConfig + { + public static KeePass.App.Configuration.AceCustomConfig _config = KeePass.Program.Config.CustomConfig; + public static LockAssistConfig GetOptions(PwDatabase db) + { + LockAssistConfig conf = new LockAssistConfig(); + conf.GetOptionsInternal(db); + return conf; + } + + private void GetOptionsInternal(PwDatabase db) + { + QU_DBSpecific = (db != null) && (db.IsOpen) && db.CustomData.Exists(LockAssistQuickUnlockDBSpecific); + if (QU_DBSpecific) + { + QU_Active = db.CustomData.Get(LockAssistActive) == "true"; + QU_UsePassword = db.CustomData.Get(LockAssistUsePassword) == "true"; + if (!int.TryParse(db.CustomData.Get(LockAssistKeyLength), out QU_PINLength)) QU_PINLength = 4; + QU_UsePasswordFromEnd = db.CustomData.Get(LockAssistKeyFromEnd) == "false"; + } + else + { + QU_Active = _config.GetBool(LockAssistActive, false); + QU_UsePassword = _config.GetBool(LockAssistUsePassword, true); + QU_PINLength = (int)_config.GetLong(LockAssistKeyLength, 4); + QU_UsePasswordFromEnd = _config.GetBool(LockAssistKeyFromEnd, true); + } + } + + public static bool FirstTime + { + get { return _config.GetBool(LockAssistFirstTime, true); } + set { _config.SetBool(LockAssistFirstTime, value); } + } + + public bool QU_Active = false; + public bool QU_DBSpecific = false; + public bool QU_UsePassword = true; + public bool QU_UsePasswordFromEnd = true; + public int QU_PINLength = 4; + + public bool ConfigChanged(LockAssistConfig comp, bool CheckDBSpecific) + { + if (QU_Active != comp.QU_Active) return true; + if (CheckDBSpecific && (QU_DBSpecific != comp.QU_DBSpecific)) return true; + if (QU_UsePassword != comp.QU_UsePassword) return true; + if (QU_PINLength != comp.QU_PINLength) return true; + if (QU_UsePasswordFromEnd != comp.QU_UsePasswordFromEnd) return true; + return false; + } + + public bool CopyFrom(LockAssistConfig NewOptions) + { + bool SwitchToNoDBSpecific = QU_DBSpecific && !NewOptions.QU_DBSpecific; + QU_DBSpecific = NewOptions.QU_DBSpecific; + QU_Active = NewOptions.QU_Active; + QU_UsePassword = NewOptions.QU_UsePassword; + QU_PINLength = NewOptions.QU_PINLength; + QU_UsePasswordFromEnd = NewOptions.QU_UsePasswordFromEnd; + return SwitchToNoDBSpecific; + } + + public void WriteConfig() + { + QU_DBSpecific = false; + WriteConfig(null); + } + + public void WriteConfig(PwDatabase db) + { + if (QU_DBSpecific) + { + if (db == null || !db.IsOpen) return; + db.CustomData.Set(LockAssistActive, QU_Active ? "true" : "false"); + db.CustomData.Set(LockAssistUsePassword, QU_UsePassword ? "true" : "false"); + db.CustomData.Set(LockAssistKeyLength, QU_PINLength.ToString()); + db.CustomData.Set(LockAssistKeyFromEnd, QU_UsePasswordFromEnd ? "true" : "false"); + db.CustomData.Set(LockAssistQuickUnlockDBSpecific, "true"); + FlagDBChanged(db); + } + else + { + _config.SetBool(LockAssistActive, QU_Active); + _config.SetBool(LockAssistUsePassword, QU_UsePassword); + _config.SetLong(LockAssistKeyLength, QU_PINLength); + _config.SetBool(LockAssistKeyFromEnd, QU_UsePasswordFromEnd); + DeleteDBConfig(db); + } + } + + private void FlagDBChanged(PwDatabase db) + { + db.Modified = true; + db.SettingsChanged = DateTime.UtcNow; + KeePass.Program.MainForm.UpdateUI(false, KeePass.Program.MainForm.DocumentManager.FindDocument(db), false, null, false, null, true); + } + + public void DeleteDBConfig(PwDatabase db) + { + if (db == null || !db.IsOpen) return; + bool deleted = db.CustomData.Remove(LockAssistActive); + deleted |= deleted = db.CustomData.Remove(LockAssistUsePassword); + deleted |= db.CustomData.Remove(LockAssistKeyLength); + deleted |= db.CustomData.Remove(LockAssistKeyFromEnd); + deleted |= db.CustomData.Remove(LockAssistQuickUnlockDBSpecific); + if (deleted) + { + FlagDBChanged(db); + GetOptionsInternal(db); + } + } + + private const string LockAssistActive = "LockAssist.Active"; + private const string LockAssistUsePassword = "LockAssist.UsePassword"; + private const string LockAssistKeyLength = "LockAssist.KeyLength"; + private const string LockAssistKeyFromEnd = "LockAssist.KeyFromEnd"; + private const string LockAssistFirstTime = "LockAssist.FirstTime"; + private const string LockAssistQuickUnlockDBSpecific = "LockAssist.QuickUnlockDBSpecific"; + } +} diff --git a/src/OptionsForm.Designer.cs b/src/OptionsForm.Designer.cs new file mode 100644 index 0000000..b0ed922 --- /dev/null +++ b/src/OptionsForm.Designer.cs @@ -0,0 +1,239 @@ +namespace LockAssist +{ + partial class OptionsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tcLockAssistOptions = new System.Windows.Forms.TabControl(); + this.tabQuickUnlock = new System.Windows.Forms.TabPage(); + this.cbPINDBSpecific = new System.Windows.Forms.CheckBox(); + this.tbModeExplain = new System.Windows.Forms.TextBox(); + this.panel2 = new System.Windows.Forms.Panel(); + this.cbActive = new System.Windows.Forms.CheckBox(); + this.lQUPINLength = new System.Windows.Forms.Label(); + this.lQUMode = new System.Windows.Forms.Label(); + this.rbPINEnd = new System.Windows.Forms.RadioButton(); + this.rbPINFront = new System.Windows.Forms.RadioButton(); + this.tbPINLength = new System.Windows.Forms.TextBox(); + this.cbPINMode = new System.Windows.Forms.ComboBox(); + this.tcLockAssistOptions.SuspendLayout(); + this.tabQuickUnlock.SuspendLayout(); + this.panel2.SuspendLayout(); + this.SuspendLayout(); + // + // tcLockAssistOptions + // + this.tcLockAssistOptions.Controls.Add(this.tabQuickUnlock); + this.tcLockAssistOptions.Dock = System.Windows.Forms.DockStyle.Fill; + this.tcLockAssistOptions.Location = new System.Drawing.Point(0, 0); + this.tcLockAssistOptions.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.tcLockAssistOptions.Name = "tcLockAssistOptions"; + this.tcLockAssistOptions.SelectedIndex = 0; + this.tcLockAssistOptions.Size = new System.Drawing.Size(853, 697); + this.tcLockAssistOptions.TabIndex = 6; + // + // tabQuickUnlock + // + this.tabQuickUnlock.BackColor = System.Drawing.Color.Transparent; + this.tabQuickUnlock.Controls.Add(this.cbPINDBSpecific); + this.tabQuickUnlock.Controls.Add(this.tbModeExplain); + this.tabQuickUnlock.Controls.Add(this.panel2); + this.tabQuickUnlock.Location = new System.Drawing.Point(10, 48); + this.tabQuickUnlock.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.tabQuickUnlock.Name = "tabQuickUnlock"; + this.tabQuickUnlock.Padding = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.tabQuickUnlock.Size = new System.Drawing.Size(833, 639); + this.tabQuickUnlock.TabIndex = 0; + this.tabQuickUnlock.Text = "Quick Unlock settings"; + this.tabQuickUnlock.UseVisualStyleBackColor = true; + // + // cbPINDBSpecific + // + this.cbPINDBSpecific.AutoSize = true; + this.cbPINDBSpecific.Location = new System.Drawing.Point(20, 490); + this.cbPINDBSpecific.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.cbPINDBSpecific.Name = "cbPINDBSpecific"; + this.cbPINDBSpecific.Size = new System.Drawing.Size(354, 36); + this.cbPINDBSpecific.TabIndex = 5; + this.cbPINDBSpecific.Text = "Settings are DB specific"; + this.cbPINDBSpecific.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + this.cbPINDBSpecific.UseVisualStyleBackColor = true; + // + // tbModeExplain + // + this.tbModeExplain.Dock = System.Windows.Forms.DockStyle.Top; + this.tbModeExplain.ForeColor = System.Drawing.SystemColors.WindowText; + this.tbModeExplain.Location = new System.Drawing.Point(5, 284); + this.tbModeExplain.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.tbModeExplain.Multiline = true; + this.tbModeExplain.Name = "tbModeExplain"; + this.tbModeExplain.ReadOnly = true; + this.tbModeExplain.Size = new System.Drawing.Size(823, 178); + this.tbModeExplain.TabIndex = 34; + this.tbModeExplain.TabStop = false; + this.tbModeExplain.Text = "Requirements for mode \'Database password\'\r\n - Database masterkey contains a passw" + + "ord\r\n - Option \'Remember master password\' is active\r\n\r\nQuick Unlock Entry will b" + + "e used as fallback"; + // + // panel2 + // + this.panel2.Controls.Add(this.cbActive); + this.panel2.Controls.Add(this.lQUPINLength); + this.panel2.Controls.Add(this.lQUMode); + this.panel2.Controls.Add(this.rbPINEnd); + this.panel2.Controls.Add(this.rbPINFront); + this.panel2.Controls.Add(this.tbPINLength); + this.panel2.Controls.Add(this.cbPINMode); + this.panel2.Dock = System.Windows.Forms.DockStyle.Top; + this.panel2.Location = new System.Drawing.Point(5, 5); + this.panel2.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(823, 279); + this.panel2.TabIndex = 35; + // + // cbActive + // + this.cbActive.AutoSize = true; + this.cbActive.Location = new System.Drawing.Point(14, 17); + this.cbActive.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.cbActive.Name = "cbActive"; + this.cbActive.Size = new System.Drawing.Size(317, 36); + this.cbActive.TabIndex = 42; + this.cbActive.Text = "Enable Quick Unlock"; + this.cbActive.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + this.cbActive.UseVisualStyleBackColor = true; + // + // lQUPINLength + // + this.lQUPINLength.AutoSize = true; + this.lQUPINLength.Location = new System.Drawing.Point(7, 133); + this.lQUPINLength.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.lQUPINLength.Name = "lQUPINLength"; + this.lQUPINLength.Size = new System.Drawing.Size(155, 32); + this.lQUPINLength.TabIndex = 41; + this.lQUPINLength.Text = "PIN length:"; + // + // lQUMode + // + this.lQUMode.AutoSize = true; + this.lQUMode.Location = new System.Drawing.Point(7, 68); + this.lQUMode.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.lQUMode.Name = "lQUMode"; + this.lQUMode.Size = new System.Drawing.Size(94, 32); + this.lQUMode.TabIndex = 40; + this.lQUMode.Text = "Mode:"; + // + // rbPINEnd + // + this.rbPINEnd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.rbPINEnd.AutoSize = true; + this.rbPINEnd.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.rbPINEnd.Location = new System.Drawing.Point(483, 225); + this.rbPINEnd.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.rbPINEnd.Name = "rbPINEnd"; + this.rbPINEnd.Size = new System.Drawing.Size(334, 36); + this.rbPINEnd.TabIndex = 39; + this.rbPINEnd.TabStop = true; + this.rbPINEnd.Text = "Use {0} last characters"; + this.rbPINEnd.UseVisualStyleBackColor = true; + // + // rbPINFront + // + this.rbPINFront.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.rbPINFront.AutoSize = true; + this.rbPINFront.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.rbPINFront.Location = new System.Drawing.Point(484, 178); + this.rbPINFront.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.rbPINFront.Name = "rbPINFront"; + this.rbPINFront.Size = new System.Drawing.Size(335, 36); + this.rbPINFront.TabIndex = 38; + this.rbPINFront.TabStop = true; + this.rbPINFront.Text = "Use {0} first characters"; + this.rbPINFront.UseVisualStyleBackColor = true; + // + // tbPINLength + // + this.tbPINLength.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tbPINLength.Location = new System.Drawing.Point(639, 129); + this.tbPINLength.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.tbPINLength.MaxLength = 3; + this.tbPINLength.Name = "tbPINLength"; + this.tbPINLength.Size = new System.Drawing.Size(175, 38); + this.tbPINLength.TabIndex = 37; + this.tbPINLength.Tag = "32"; + this.tbPINLength.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.tbPINLength.TextChanged += new System.EventHandler(this.tbPINLength_TextChanged); + // + // cbPINMode + // + this.cbPINMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cbPINMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbPINMode.FormattingEnabled = true; + this.cbPINMode.Items.AddRange(new object[] { + "Quick Unlock entry only", + "Database password"}); + this.cbPINMode.Location = new System.Drawing.Point(189, 64); + this.cbPINMode.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.cbPINMode.Name = "cbPINMode"; + this.cbPINMode.Size = new System.Drawing.Size(624, 39); + this.cbPINMode.TabIndex = 36; + this.cbPINMode.SelectedIndexChanged += new System.EventHandler(this.cbPINMode_SelectedIndexChanged); + // + // OptionsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Transparent; + this.Controls.Add(this.tcLockAssistOptions); + this.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5); + this.Name = "OptionsForm"; + this.Size = new System.Drawing.Size(853, 697); + this.Load += new System.EventHandler(this.UnlockOptions_Load); + this.tcLockAssistOptions.ResumeLayout(false); + this.tabQuickUnlock.ResumeLayout(false); + this.tabQuickUnlock.PerformLayout(); + this.panel2.ResumeLayout(false); + this.panel2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TabControl tcLockAssistOptions; + private System.Windows.Forms.TabPage tabQuickUnlock; + private System.Windows.Forms.CheckBox cbPINDBSpecific; + private System.Windows.Forms.TextBox tbModeExplain; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.CheckBox cbActive; + private System.Windows.Forms.Label lQUPINLength; + private System.Windows.Forms.Label lQUMode; + private System.Windows.Forms.RadioButton rbPINEnd; + private System.Windows.Forms.RadioButton rbPINFront; + private System.Windows.Forms.TextBox tbPINLength; + internal System.Windows.Forms.ComboBox cbPINMode; + } +} \ No newline at end of file diff --git a/src/OptionsForm.cs b/src/OptionsForm.cs new file mode 100644 index 0000000..16fe6b7 --- /dev/null +++ b/src/OptionsForm.cs @@ -0,0 +1,137 @@ +using System; +using System.Windows.Forms; + +using KeePass; +using KeePassLib; +using KeePass.UI; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public partial class OptionsForm : UserControl + { + private bool FirstTime = false; + + public OptionsForm() + { + InitializeComponent(); + + Text = PluginTranslate.PluginName; + + cbActive.Text = PluginTranslate.Active; + tabQuickUnlock.Text = PluginTranslate.OptionsQUSettings; + lQUMode.Text = PluginTranslate.OptionsQUMode; + lQUPINLength.Text = PluginTranslate.OptionsQUPINLength; + tbModeExplain.Lines = PluginTranslate.OptionsQUReqInfoDB.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); ; + cbPINDBSpecific.Text = PluginTranslate.OptionsQUSettingsPerDB; + cbPINMode.Items.Clear(); + cbPINMode.Items.AddRange(new string[] { PluginTranslate.OptionsQUModeEntry, PluginTranslate.OptionsQUModeDatabasePW }); + } + + internal void InitEx(LockAssistConfig options) + { + cbActive.Checked = options.QU_Active; + cbPINMode.SelectedIndex = options.QU_UsePassword ? 1 : 0; + tbPINLength.Text = options.QU_PINLength.ToString(); + rbPINEnd.Checked = options.QU_UsePasswordFromEnd; + rbPINFront.Checked = !options.QU_UsePasswordFromEnd; + cbPINDBSpecific.Checked = options.QU_DBSpecific; + FirstTime = LockAssistConfig.FirstTime; + } + + internal LockAssistConfig GetOptions() + { + LockAssistConfig options = new LockAssistConfig(); + options.QU_Active = cbActive.Checked; + options.QU_UsePassword = cbPINMode.SelectedIndex == 1; + if (!int.TryParse(tbPINLength.Text, out options.QU_PINLength)) options.QU_PINLength = 4; + options.QU_UsePasswordFromEnd = rbPINEnd.Checked; + options.QU_DBSpecific = cbPINDBSpecific.Checked; + + return options; + } + + private void tbPINLength_TextChanged(object sender, EventArgs e) + { + int len = 0; + if (!int.TryParse(tbPINLength.Text, out len)) len = 4; + if (len < 1) len = 1; + if (len > 32) len = 32; + rbPINFront.Text = string.Format(PluginTranslate.OptionsQUUseFirst, len.ToString()); + rbPINEnd.Text = string.Format(PluginTranslate.OptionsQUUseLast, len.ToString()); + } + + private void cbPINMode_SelectedIndexChanged(object sender, EventArgs e) + { + lQUMode.ForeColor = System.Drawing.SystemColors.ControlText; + if (cbPINMode.SelectedIndex == 0) + { + PwEntry check = QuickUnlock.GetQuickUnlockEntry(Program.MainForm.ActiveDatabase); + if (check != null) return; + if (!FirstTime && (Tools.AskYesNo(PluginTranslate.OptionsQUEntryCreate) == DialogResult.No)) + { + cbPINMode.SelectedIndex = 1; + } + else + { + check = new PwEntry(true, true); + Program.MainForm.ActiveDatabase.RootGroup.AddEntry(check, true); + check.Strings.Set(PwDefs.TitleField, new KeePassLib.Security.ProtectedString(false, QuickUnlockKeyProv.KeyProviderName)); + Tools.ShowInfo(PluginTranslate.OptionsQUEntryCreated); + ShowQuickUnlockEntry(Program.MainForm.ActiveDatabase, check.Uuid); + } + return; + } + if (Program.Config.Security.MasterPassword.RememberWhileOpen) return; + lQUMode.ForeColor = System.Drawing.Color.Red; + if (FirstTime || (cbPINMode.SelectedIndex == 0)) return; + Tools.ShowInfo(PluginTranslate.OptionsQUInfoRememberPassword); + } + + private void tbValidating(object sender, System.ComponentModel.CancelEventArgs e) + { + int len = 0; + if (!int.TryParse((sender as TextBox).Text, out len)) + { + if ((sender as TextBox).Name == "tbPinLength") len = 4; + else len = 60; + } + if ((sender as TextBox).Name == "tbPinLength") len = Math.Max(1, len); + if ((sender as TextBox).Name != "tbPinLength") len = Math.Max(0, len); + int max = int.Parse((string)(sender as TextBox).Tag); + if (len > max) len = max; + if ((sender as TextBox).Text != len.ToString()) (sender as TextBox).Text = len.ToString(); + } + + private void ShowQuickUnlockEntry(PwDatabase db, PwUuid qu) + { + Program.MainForm.UpdateUI(false, null, false, db.RootGroup, true, db.RootGroup, true); + Program.MainForm.EnsureVisibleEntry(qu); + + ListView lv = (Program.MainForm.Controls.Find("m_lvEntries", true)[0] as ListView); + foreach (ListViewItem lvi in lv.Items) + { + PwListItem li = (lvi.Tag as PwListItem); + if (li == null) continue; + + PwEntry pe = li.Entry; + if (pe.Uuid != qu) continue; + lv.FocusedItem = lvi; + ToolStripItem[] tsmi = Program.MainForm.EntryContextMenu.Items.Find("m_ctxEntryEdit", false); + if (tsmi != null) tsmi[0].PerformClick(); + break; + } + } + + private void UnlockOptions_Load(object sender, EventArgs e) + { + if ((Program.MainForm.ActiveDatabase == null) || !Program.MainForm.ActiveDatabase.IsOpen) + { + cbPINDBSpecific.Enabled = false; + cbPINDBSpecific.Checked = false; + } + } + } +} diff --git a/src/PluginTranslation.cs b/src/PluginTranslation.cs new file mode 100644 index 0000000..3fd31d2 --- /dev/null +++ b/src/PluginTranslation.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; +using System.IO; +using System.Reflection; + +using KeePass.Plugins; +using KeePass.Util; +using KeePassLib.Utility; + +using PluginTools; +using System.Windows.Forms; + +namespace PluginTranslation +{ + public class TranslationChangedEventArgs: EventArgs + { + public string OldLanguageIso6391 = string.Empty; + public string NewLanguageIso6391 = string.Empty; + + public TranslationChangedEventArgs(string OldLanguageIso6391, string NewLanguageIso6391) + { + this.OldLanguageIso6391 = OldLanguageIso6391; + this.NewLanguageIso6391 = NewLanguageIso6391; + } + } + + public static class PluginTranslate + { + public static long TranslationVersion = 0; + public static event EventHandler TranslationChanged = null; + private static string LanguageIso6391 = string.Empty; + #region Definitions of translated texts go here + public const string PluginName = "Lock Assist"; + public static readonly string FirstTimeInfo = @"Quick Unlock offers two operation modes. +Please choose your preferred way of working."; + public static readonly string OptionsQUMode = @"Mode:"; + public static readonly string Active = @"Quick Unlock active"; + public static readonly string KeyProvNoQuickUnlock = @"No Quick Unlock key found. + +Quick Unlock is not possible."; + public static readonly string OptionsQUReqInfoDB = @"Prerequsites for mode 'database password': +- Database masterkey contains a password +- Option 'Remember master password' is active + +An existing Quick Unlock entry will be used as fallback"; + public static readonly string OptionsQUSettings = @"Quick Unlock"; + public static readonly string OptionsQUModeEntry = @"Quick Unlock entry only"; + public static readonly string OptionsQUModeDatabasePW = @"Database password"; + public static readonly string OptionsQUEntryCreated = @"Quick Unlock entry created. + +Please edit and set Quick Unlock PIN as password"; + public static readonly string OptionsQUSettingsPerDB = @"Settings are DB specific"; + public static readonly string OptionsSwitchDBToGeneral = @"Database specific settings switched off. + +Click '{0}' to use the global settings for this database. +Click '{1}' to make this database's settings the new global settings."; + public static readonly string OptionsQUPINLength = @"PIN length:"; + public static readonly string ButtonUnlock = @"Unlock"; + public static readonly string UnlockLabel = @"Quick Unlock PIN:"; + public static readonly string OptionsQUEntryCreate = @"Quick Unlock entry could not be found. + +Create it now?"; + public static readonly string KeyProvNoCreate = @"This key provider cannot be used to create keys."; + public static readonly string OptionsQUInfoRememberPassword = @"'Remember master password' needs to be active in Options -> Security. +Please don't forget to activate this setting"; + public static readonly string OptionsQUUseLast = @"Use last {0} characters as PIN"; + public static readonly string OptionsQUUseFirst = @"Use first {0} characters as PIN"; + public static readonly string WrongPIN = @"The entered PIN was not correct. + +The database stays locked and can only be unlocked with the original masterkey"; + #endregion + + #region NO changes in this area + private static StringDictionary m_translation = new StringDictionary(); + + public static void Init(Plugin plugin, string LanguageCodeIso6391) + { + List lDebugStrings = new List(); + m_translation.Clear(); + bool bError = true; + LanguageCodeIso6391 = InitTranslation(plugin, lDebugStrings, LanguageCodeIso6391, out bError); + if (bError && (LanguageCodeIso6391.Length > 2)) + { + LanguageCodeIso6391 = LanguageCodeIso6391.Substring(0, 2); + lDebugStrings.Add("Trying fallback: " + LanguageCodeIso6391); + LanguageCodeIso6391 = InitTranslation(plugin, lDebugStrings, LanguageCodeIso6391, out bError); + } + if (bError) + { + PluginDebug.AddError("Reading translation failed", 0, lDebugStrings.ToArray()); + LanguageCodeIso6391 = "en"; + } + else + { + List lTranslatable = new List( + typeof(PluginTranslate).GetFields(BindingFlags.Static | BindingFlags.Public) + ).FindAll(x => x.IsInitOnly); + lDebugStrings.Add("Parsing complete"); + lDebugStrings.Add("Translated texts read: " + m_translation.Count.ToString()); + lDebugStrings.Add("Translatable texts: " + lTranslatable.Count.ToString()); + foreach (FieldInfo f in lTranslatable) + { + if (m_translation.ContainsKey(f.Name)) + { + lDebugStrings.Add("Key found: " + f.Name); + f.SetValue(null, m_translation[f.Name]); + } + else + lDebugStrings.Add("Key not found: " + f.Name); + } + PluginDebug.AddInfo("Reading translations finished", 0, lDebugStrings.ToArray()); + } + if (TranslationChanged != null) + { + TranslationChanged(null, new TranslationChangedEventArgs(LanguageIso6391, LanguageCodeIso6391)); + } + LanguageIso6391 = LanguageCodeIso6391; + lDebugStrings.Clear(); + } + + private static string InitTranslation(Plugin plugin, List lDebugStrings, string LanguageCodeIso6391, out bool bError) + { + if (string.IsNullOrEmpty(LanguageCodeIso6391)) + { + lDebugStrings.Add("No language identifier supplied, using 'en' as fallback"); + LanguageCodeIso6391 = "en"; + } + string filename = GetFilename(plugin.GetType().Namespace, LanguageCodeIso6391); + lDebugStrings.Add("Translation file: " + filename); + + if (!File.Exists(filename)) //If e. g. 'plugin.zh-tw.language.xml' does not exist, try 'plugin.zh.language.xml' + { + lDebugStrings.Add("File does not exist"); + bError = true; + return LanguageCodeIso6391; + } + else + { + string translation = string.Empty; + try { translation = File.ReadAllText(filename); } + catch (Exception ex) + { + lDebugStrings.Add("Error reading file: " + ex.Message); + LanguageCodeIso6391 = "en"; + bError = true; + return LanguageCodeIso6391; + } + XmlSerializer xs = new XmlSerializer(m_translation.GetType()); + lDebugStrings.Add("File read, parsing content"); + try + { + m_translation = (StringDictionary)xs.Deserialize(new StringReader(translation)); + } + catch (Exception ex) + { + lDebugStrings.Add("Error parsing file: " + ex.Message); + LanguageCodeIso6391 = "en"; + MessageBox.Show("Error parsing translation file\n" + ex.Message, PluginName, MessageBoxButtons.OK, MessageBoxIcon.Error); + bError = true; + return LanguageCodeIso6391; + } + bError = false; + return LanguageCodeIso6391; + } + } + + private static string GetFilename(string plugin, string lang) + { + string filename = UrlUtil.GetFileDirectory(WinUtil.GetExecutable(), true, true); + filename += KeePass.App.AppDefs.PluginsDir + UrlUtil.LocalDirSepChar + "Translations" + UrlUtil.LocalDirSepChar; + filename += plugin + "." + lang + ".language.xml"; + return filename; + } + #endregion + } + + #region NO changes in this area + [XmlRoot("Translation")] + public class StringDictionary : Dictionary, IXmlSerializable + { + public System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + bool wasEmpty = reader.IsEmptyElement; + reader.Read(); + if (wasEmpty) return; + bool bFirst = true; + while (reader.NodeType != XmlNodeType.EndElement) + { + if (bFirst) + { + bFirst = false; + try + { + reader.ReadStartElement("TranslationVersion"); + PluginTranslate.TranslationVersion = reader.ReadContentAsLong(); + reader.ReadEndElement(); + } + catch { } + } + reader.ReadStartElement("item"); + reader.ReadStartElement("key"); + string key = reader.ReadContentAsString(); + reader.ReadEndElement(); + reader.ReadStartElement("value"); + string value = reader.ReadContentAsString(); + reader.ReadEndElement(); + this.Add(key, value); + reader.ReadEndElement(); + reader.MoveToContent(); + } + reader.ReadEndElement(); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteStartElement("TranslationVersion"); + writer.WriteString(PluginTranslate.TranslationVersion.ToString()); + writer.WriteEndElement(); + foreach (string key in this.Keys) + { + writer.WriteStartElement("item"); + writer.WriteStartElement("key"); + writer.WriteString(key); + writer.WriteEndElement(); + writer.WriteStartElement("value"); + writer.WriteString(this[key]); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + } + } + #endregion +} \ No newline at end of file diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1fb4362 --- /dev/null +++ b/src/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("LockAssist")] +[assembly: AssemblyDescription("Enhance KeePass database locking & unlocking")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Rookiestyle")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(true)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("4712d887-6685-4cb1-b67b-e981119fe3d2")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0")] +[assembly: AssemblyFileVersion("1.0.0")] diff --git a/src/QuickUnlock.cs b/src/QuickUnlock.cs new file mode 100644 index 0000000..d98906d --- /dev/null +++ b/src/QuickUnlock.cs @@ -0,0 +1,263 @@ +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Keys; +using KeePassLib.Security; +using KeePassLib.Serialization; +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + /* + * QuickUnlock part is inspired by KeePass2Android's QuickUnlock and https://github.com/JanisEst/KeePassQuickUnlock + * Additional features added: + * - DB specific settings + * - Restore previously used masterkey (allow QuickUnlock multiple times in a row, allow printing of emergency sheets, ...) + * - Show explicit error message if wrong QuickUnlock key is entered + * - Automate creation of QuickUnlock entry + * - Additional smaller adjustments + */ + internal partial class QuickUnlock + { + private QuickUnlockKeyProv m_kp = null; + + public QuickUnlock() + { + Init(); + } + private void Init() + { + m_kp = new QuickUnlockKeyProv(); + Program.KeyProviderPool.Add(m_kp); + Program.MainForm.FileClosingPre += OnFileClosePre_QU; + Program.MainForm.FileOpened += OnFileOpened_QU; + GlobalWindowManager.WindowAdded += OnWindowAdded_QU; + PluginDebug.AddInfo("Quick Unlock: Initialized", 0); + } + + #region Eventhandler for opening and closing a DB + private void OnFileOpened_QU(object sender, FileOpenedEventArgs e) + { + var MyOptions = LockAssistConfig.GetOptions(e.Database); + if (LockAssistConfig.FirstTime && + (!MyOptions.QU_UsePassword && (GetQuickUnlockEntry(e.Database) == null) + || (MyOptions.QU_UsePassword && !Program.Config.Security.MasterPassword.RememberWhileOpen))) + { + Tools.ShowInfo(PluginTranslate.FirstTimeInfo); + Tools.ShowOptions(); + LockAssistConfig.FirstTime = false; + } + if (!MyOptions.QU_Active) return; + //Restore previously stored information about the masterkey + QuickUnlockOldKeyInfo quOldKey = QuickUnlockKeyProv.GetOldKey(e.Database); + if (quOldKey == null) + { + PluginDebug.AddInfo("Quick Unlock: DB opened, no encrypted master key available to restore"); + return; + } + PluginDebug.AddInfo("Quick Unlock: DB opened, restore encrypted master key"); + KcpCustomKey ck = (KcpCustomKey)e.Database.MasterKey.GetUserKey(typeof(KcpCustomKey)); + if ((ck == null) || (ck.Name != QuickUnlockKeyProv.KeyProviderName)) + { + //Quick Unlock was not used + return; + } + e.Database.MasterKey.RemoveUserKey(ck); + if (quOldKey.pwHash != null) + { + KcpPassword p; + p = QuickUnlockKeyProv.DeserializePassword(quOldKey.pwHash, Program.Config.Security.MasterPassword.RememberWhileOpen); + if (p.Password == null && quOldKey.HasPassword) p = new KcpPassword(new byte[0] { }, Program.Config.Security.MasterPassword.RememberWhileOpen); + e.Database.MasterKey.AddUserKey(p); + } + if (!string.IsNullOrEmpty(quOldKey.keyFile)) e.Database.MasterKey.AddUserKey(new KcpKeyFile(quOldKey.keyFile)); + if (quOldKey.account) e.Database.MasterKey.AddUserKey(new KcpUserAccount()); + Program.Config.Defaults.SetKeySources(e.Database.IOConnectionInfo, e.Database.MasterKey); + } + + private void OnFileClosePre_QU(object sender, FileClosingEventArgs e) + { + //Do quick unlock only in case of locking + //Do NOT do quick unlock in case of closing the database + PluginDebug.AddInfo("Quick Unlock: File closing = " + e.Flags.ToString()); + if (e.Flags != FileEventFlags.Locking) + { + QuickUnlockKeyProv.RemoveDb(e.Database); + return; + } + var MyOptions = LockAssistConfig.GetOptions(e.Database); + if (!MyOptions.QU_Active) return; + ProtectedString QuickUnlockKey = null; + if (Program.Config.Security.MasterPassword.RememberWhileOpen && MyOptions.QU_UsePassword) + QuickUnlockKey = GetQuickUnlockKeyFromMasterKey(e.Database); + if (QuickUnlockKey == null) + QuickUnlockKey = GetQuickUnlockKeyFromEntry(e.Database); + QuickUnlockKey = TrimQuickUnlockKey(QuickUnlockKey, MyOptions); + if (QuickUnlockKey == null) + { + PluginDebug.AddError("Quick Unlock: Can't derive key, Quick Unlock not possible"); + return; + } + QuickUnlockKeyProv.AddDb(e.Database, QuickUnlockKey, Program.Config.Security.MasterPassword.RememberWhileOpen && MyOptions.QU_UsePassword); + PluginDebug.AddInfo("Quick Unlock: key added"); + } + #endregion + + #region Unlock / KeyPromptForm + private void OnWindowAdded_QU(object sender, GwmWindowEventArgs e) + { + if (!(e.Form is KeyPromptForm) && !(e.Form is KeyCreationForm)) return; + PluginDebug.AddInfo(e.Form.GetType().Name + " added", 0); + e.Form.Shown += (o, x) => OnKeyFormShown_QU(o, false); + } + + public static void OnKeyFormShown_QU(object sender, bool resetFile) + { + Form keyform = (sender as Form); + try + { + ComboBox cmbKeyFile = (ComboBox)Tools.GetControl("m_cmbKeyFile", keyform); + if (cmbKeyFile == null) + { + PluginDebug.AddError("Cant't find m_cmbKeyFile'", 0, "Form: " + keyform.GetType().Name); + return; + } + int index = cmbKeyFile.Items.IndexOf(QuickUnlockKeyProv.KeyProviderName); + //Quick Unlock cannot be used to create a key ==> Remove it from list of key providers + if (keyform is KeyCreationForm) + { + PluginDebug.AddInfo("Removing Quick Unlock from key providers", 0); + if (index == -1) return; + cmbKeyFile.Items.RemoveAt(index); + List keyfiles = (List)Tools.GetField("m_lKeyFileNames", keyform); + if (keyfiles != null) keyfiles.Remove(QuickUnlockKeyProv.KeyProviderName); + return; + } + + //Key prompt form is shown + IOConnectionInfo dbIOInfo = (IOConnectionInfo)Tools.GetField("m_ioInfo", keyform); + //If Quick Unlock is possible show the Quick Unlock form + if ((index != -1) && (dbIOInfo != null) && QuickUnlockKeyProv.HasDB(dbIOInfo.Path)) + { + cmbKeyFile.SelectedIndex = index; + CheckBox cbPassword = (CheckBox)Tools.GetControl("m_cbPassword", keyform); + CheckBox cbAccount = (CheckBox)Tools.GetControl("m_cbUserAccount", keyform); + Button bOK = (Button)Tools.GetControl("m_btnOK", keyform); + if ((bOK != null) && (cbPassword != null) && (cbAccount != null)) + { + UIUtil.SetChecked(cbPassword, false); + UIUtil.SetChecked(cbAccount, false); + bOK.PerformClick(); + } + else + { + PluginDebug.AddError("Quick Unlock form cannot be shown", 0, + "Form: "+keyform.GetType().Name, + "Password checkbox: " + (cbPassword == null ? "null" : cbPassword.Name + " / " + cbPassword.GetType().Name), + "Account checkbox: " + (cbAccount == null ? "null" : cbAccount.Name + " / " + cbAccount.GetType().Name), + "OK button: " + (bOK == null ? "null" : bOK.Name + " / " + bOK.GetType().Name) + ); + } + return; + } + + //Quick Unlock is not possible => Remove it from list of key providers + if ((resetFile || ((dbIOInfo != null) && !QuickUnlockKeyProv.HasDB(dbIOInfo.Path))) && (index != -1)) + { + cmbKeyFile.Items.RemoveAt(index); + List keyfiles = (List)Tools.GetField("m_lKeyFileNames", keyform); + if (keyfiles != null) keyfiles.Remove(QuickUnlockKeyProv.KeyProviderName); + if (resetFile) cmbKeyFile.SelectedIndex = 0; + } + } + catch (Exception ex) + { + PluginDebug.AddError(ex.Message); + } + } + #endregion + + #region QuickUnlockKey handling + private ProtectedString GetQuickUnlockKeyFromMasterKey(PwDatabase db) + { + /* + * Try to create QuickUnlockKey based on password + * + * If no password is contained in MasterKey there is + * EITHER no password at all + * OR the database was unlocked with Quick Unlock + * In these case ask our key provider for the original password + */ + ProtectedString QuickUnlockKey = null; + try + { + KcpPassword pw = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword)); + if (pw != null) + QuickUnlockKey = pw.Password; + } + catch (Exception ex) { PluginDebug.AddError("Quick Unlock: " + ex.Message); } + if (QuickUnlockKey != null) //Do NOT check QuickUnlockKey.Length, an empty string is treated like no password otherwise + { + PluginDebug.AddInfo("Quick Unlock: Quick Unlock key found", 0); + return QuickUnlockKey; + } + PluginDebug.AddError("Quick Unlock: Quick Unlock key NOT found", 0, + "MasterPassword.RememberWhileOpen: " + Program.Config.Security.MasterPassword.RememberWhileOpen.ToString()); + return null; + } + + private ProtectedString GetQuickUnlockKeyFromEntry(PwDatabase db) + { + PwEntry QuickUnlockEntry = GetQuickUnlockEntry(db); + if (QuickUnlockEntry == null) + { + PluginDebug.AddInfo("Quick Unlock: Quick Unlock entry NOT found", 0); + return null; + } + PluginDebug.AddInfo("Quick Unlock: Quick Unlock entry found", 0); + return QuickUnlockEntry.Strings.GetSafe(PwDefs.PasswordField); + } + + public static PwEntry GetQuickUnlockEntry(PwDatabase db) + { + if ((db == null) || !db.IsOpen) return null; + SearchParameters sp = new SearchParameters(); + sp.SearchInTitles = true; + sp.ExcludeExpired = true; + sp.SearchString = QuickUnlockKeyProv.KeyProviderName; + PwObjectList entries = new PwObjectList(); + db.RootGroup.SearchEntries(sp, entries); + if ((entries == null) || (entries.UCount == 0)) return null; + return entries.GetAt(0); + } + + private ProtectedString TrimQuickUnlockKey(ProtectedString QuickUnlockKey, LockAssistConfig lac) + { + if ((QuickUnlockKey == null) || (QuickUnlockKey.Length <= lac.QU_PINLength)) return QuickUnlockKey; + int startIndex = 0; + if (!lac.QU_UsePasswordFromEnd) startIndex = lac.QU_PINLength; + QuickUnlockKey = QuickUnlockKey.Remove(startIndex, QuickUnlockKey.Length - lac.QU_PINLength); + return QuickUnlockKey; + } + #endregion + + internal void Clear() + { + Program.KeyProviderPool.Remove(m_kp); + m_kp = null; + QuickUnlockKeyProv.Clear(); + Program.MainForm.FileClosingPre -= OnFileClosePre_QU; + Program.MainForm.FileOpened -= OnFileOpened_QU; + GlobalWindowManager.WindowAdded -= OnWindowAdded_QU; + PluginDebug.AddInfo("Quick Unlock: Terminated", 0); + } + } +} diff --git a/src/QuickUnlockKeyProv.cs b/src/QuickUnlockKeyProv.cs new file mode 100644 index 0000000..621b550 --- /dev/null +++ b/src/QuickUnlockKeyProv.cs @@ -0,0 +1,271 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Security.Cryptography; + +using KeePassLib.Keys; +using KeePassLib; +using KeePassLib.Cryptography; +using KeePassLib.Security; +using KeePassLib.Utility; +using KeePassLib.Cryptography.Cipher; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + /* + * QuickUnlock part is inspired by KeePass2Android's QuickUnlock and https://github.com/JanisEst/KeePassQuickUnlock + * Additional features added: + * - DB specific settings + * - Restore previously used masterkey (allow QuickUnlock multiple times in a row, allow printing of emergency sheets, ...) + * - Show explicit error message if wrong QuickUnlock key is entered + * - Automate creation of QuickUnlock entry + * - Additional smaller adjustments + */ + public class QuickUnlockOldKeyInfo + { + public ProtectedString QuickUnlockKey = ProtectedString.EmptyEx; + public ProtectedBinary pwHash = null; + public string keyFile = string.Empty; + public bool account = false; + public ProtectedBinary PINCheck = null; + public bool HasPassword = false; + } + + public class QuickUnlockKeyProv : KeyProvider + { + public static string KeyProviderName = PluginTranslate.PluginName + " - Quick Unlock"; + private static byte[] m_PINCheck = StrUtil.Utf8.GetBytes(KeyProviderName); + public override string Name { get { return KeyProviderName; } } + public override bool SecureDesktopCompatible { get { return true; } } + public override bool Exclusive { get { return true; } } + public override bool DirectKey { get { return true; } } + public override bool GetKeyMightShowGui { get { return true; } } + + private static Dictionary m_hashedKey = new Dictionary(); + private static Dictionary m_originalKey = new Dictionary(); + + public override byte[] GetKey(KeyProviderQueryContext ctx) + { + if (ctx.CreatingNewKey) //should not happen but you never know + { + Tools.ShowError(PluginTranslate.KeyProvNoCreate); + return null; + } + + //Check for existing Quick Unlock data + ProtectedBinary encryptedKey = new ProtectedBinary(); + if (!m_hashedKey.TryGetValue(ctx.DatabasePath, out encryptedKey)) + { + Tools.ShowError(PluginTranslate.KeyProvNoQuickUnlock); + return null; + } + + var fQuickUnlock = new UnlockForm(); + if (KeePass.UI.UIUtil.ShowDialogNotValue(fQuickUnlock, DialogResult.OK)) return null; + ProtectedString psQuickUnlockKey = fQuickUnlock.QuickUnlockKey; + KeePass.UI.UIUtil.DestroyForm(fQuickUnlock); + + //Remove Quick Unlock data - there is only one attempt + m_hashedKey.Remove(ctx.DatabasePath); + if (KeePass.UI.GlobalWindowManager.TopWindow is KeePass.Forms.KeyPromptForm && !VerifyPin(ctx, psQuickUnlockKey)) + { + QuickUnlock.OnKeyFormShown_QU(KeePass.UI.GlobalWindowManager.TopWindow, true); + Tools.ShowError(PluginTranslate.WrongPIN); + return null; + } + return DecryptKey(psQuickUnlockKey, encryptedKey).ReadData(); + } + + private bool VerifyPin(KeyProviderQueryContext ctx, ProtectedString psQuickUnlockKey) + { + //Verify Quick Unlock PIN + QuickUnlockOldKeyInfo quOldKey = null; + if (!m_originalKey.TryGetValue(ctx.DatabasePath, out quOldKey)) return false; + byte[] comparePIN = DecryptKey(psQuickUnlockKey, quOldKey.PINCheck).ReadData(); + return StrUtil.Utf8.GetString(comparePIN) == KeyProviderName; + } + + public static QuickUnlockOldKeyInfo GetOldKey(PwDatabase db) + { + QuickUnlockOldKeyInfo quOldKey = null; + if (!m_originalKey.TryGetValue(db.IOConnectionInfo.Path, out quOldKey)) return null; + if ((quOldKey.pwHash != null) && (quOldKey.pwHash.Length != 0)) + quOldKey.pwHash = DecryptKey(quOldKey.QuickUnlockKey, quOldKey.pwHash); + m_originalKey.Remove(db.IOConnectionInfo.Path); + return quOldKey; + } + + public static void AddDb(PwDatabase db, ProtectedString QuickUnlockKey, bool savePw) + { + RemoveDb(db); + ProtectedBinary pbKey = CreateMasterKeyHash(db.MasterKey); + m_hashedKey.Add(db.IOConnectionInfo.Path, EncryptKey(QuickUnlockKey, pbKey)); + AddOldMasterKey(db, QuickUnlockKey, savePw); + } + + private static void AddOldMasterKey(PwDatabase db, ProtectedString QuickUnlockKey, bool savePw) + { + QuickUnlockOldKeyInfo quOldKey = new QuickUnlockOldKeyInfo(); + quOldKey.QuickUnlockKey = QuickUnlockKey; + quOldKey.HasPassword = db.MasterKey.ContainsType(typeof(KcpPassword)); + if (quOldKey.HasPassword) + { + var pbPasswordSerialized = SerializePassword(db.MasterKey.GetUserKey(typeof(KcpPassword)) as KcpPassword, savePw); + quOldKey.pwHash = EncryptKey(QuickUnlockKey, pbPasswordSerialized); + } + if (db.MasterKey.ContainsType(typeof(KcpKeyFile))) + quOldKey.keyFile = (db.MasterKey.GetUserKey(typeof(KcpKeyFile)) as KcpKeyFile).Path; + quOldKey.account = db.MasterKey.ContainsType(typeof(KcpUserAccount)); + quOldKey.PINCheck = EncryptKey(QuickUnlockKey, new ProtectedBinary(true, m_PINCheck)); + m_originalKey.Add(db.IOConnectionInfo.Path, quOldKey); + } + + public static void Clear() + { + m_hashedKey.Clear(); + m_originalKey.Clear(); + } + + public static bool HasDB(string db) + { + if (m_hashedKey.ContainsKey(db)) return true; + m_originalKey.Remove(db); + return false; + } + + public static void RemoveDb(PwDatabase db) + { + if (db == null) return; + if (!string.IsNullOrEmpty(db.IOConnectionInfo.Path)) + { + RemoveDb(db.IOConnectionInfo.Path); + return; + } + KeePass.UI.PwDocument doc = KeePass.Program.MainForm.DocumentManager.FindDocument(db); + if ((doc == null) || string.IsNullOrEmpty(doc.LockedIoc.Path)) return; + RemoveDb(doc.LockedIoc.Path); + } + + public static void RemoveDb(string ioc) + { + bool bRemoved = m_hashedKey.Remove(ioc); + bRemoved |= m_originalKey.Remove(ioc); + if (bRemoved) PluginDebug.AddInfo("Quick Unlock - Removed Quick Unlock data", 10, "Database: " + ioc); + } + + private static ProtectedBinary CreateMasterKeyHash(CompositeKey mk) + { + List keys = new List(); + int keysLength = 0; + foreach (var key in mk.UserKeys) //Hopefully we never need to consider the sequence... + { + ProtectedBinary pb = key.KeyData; + if (pb != null) + { + var pbArray = pb.ReadData(); + keys.Add(pbArray); + keysLength += pbArray.Length; + MemUtil.ZeroByteArray(pbArray); + } + } + + byte[] allKeys = new byte[keysLength]; + int index = 0; + foreach (byte[] key in keys) + { + Array.Copy(key, 0, allKeys, index, key.Length); + index += key.Length; + MemUtil.ZeroByteArray(key); + } + + var result = new ProtectedBinary(true, allKeys); + MemUtil.ZeroByteArray(allKeys); + return result; + } + + private static ProtectedBinary SerializePassword(KcpPassword p, bool savePassword) + { + //returned array always contains password hash + //password is contained only if requested + //check for p.Password != null as the user might disable Program.Config.Security.MasterPassword.RememberWhileOpen anytime + if (savePassword && (p.Password != null) && !p.Password.IsEmpty) + { + byte[] result = new byte[p.KeyData.Length + p.Password.ReadUtf8().Length]; + Array.Copy(p.KeyData.ReadData(), result, p.KeyData.Length); + Array.Copy(p.Password.ReadUtf8(), 0, result, p.KeyData.Length, p.Password.ReadUtf8().Length); + return new ProtectedBinary(true, result); + } + return p.KeyData; + } + + public static KcpPassword DeserializePassword(ProtectedBinary serialized, bool setPassword) + { + //if password is stored and should be retrieved + //simply create a new instance + //instead of messing around with private members + KcpPassword p = new KcpPassword(string.Empty); + if (setPassword && (serialized.Length > p.KeyData.Length)) + { + byte[] pw = new byte[serialized.Length - p.KeyData.Length]; + Array.Copy(serialized.ReadData(), p.KeyData.Length, pw, 0, pw.Length); + ProtectedString pws = new ProtectedString(true, pw); + MemUtil.ZeroByteArray(pw); + return new KcpPassword(pws.ReadString()); + } + + if (serialized.Length != p.KeyData.Length) return null; + + ProtectedBinary pb = new ProtectedBinary(true, serialized.ReadData()); + typeof(KcpPassword).GetField("m_pbKeyData", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(p, pb); + typeof(KcpPassword).GetField("m_psPassword", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(p, null); + return p; + } + + private static ProtectedBinary EncryptKey(ProtectedString QuickUnlockKey, ProtectedBinary pbKey) + { + byte[] iv = CryptoRandom.Instance.GetRandomBytes(12); + ChaCha20Cipher cipher = new ChaCha20Cipher(AdjustQuickUnlockKey(QuickUnlockKey), iv); + + byte[] bKey = pbKey.ReadData(); + cipher.Encrypt(bKey, 0, bKey.Length); + + byte[] result = new byte[iv.Length + bKey.Length]; + iv.CopyTo(result, 0); + bKey.CopyTo(result, iv.Length); + MemUtil.ZeroByteArray(bKey); + + var pbResult = new ProtectedBinary(true, result); + MemUtil.ZeroByteArray(result); + return pbResult; + } + + private static ProtectedBinary DecryptKey(ProtectedString QuickUnlockKey, ProtectedBinary pbCrypted) + { + byte[] crypted = pbCrypted.ReadData(); + byte[] iv = new byte[12]; + Array.Copy(crypted, iv, iv.Length); + + byte[] cryptedKey = new byte[crypted.Length - iv.Length]; + Array.Copy(crypted, iv.Length, cryptedKey, 0, cryptedKey.Length); + + ChaCha20Cipher cipher = new ChaCha20Cipher(AdjustQuickUnlockKey(QuickUnlockKey), iv); + cipher.Decrypt(cryptedKey, 0, cryptedKey.Length); + ProtectedBinary pbDecrypted = new ProtectedBinary(true, cryptedKey); + MemUtil.ZeroByteArray(cryptedKey); + return pbDecrypted; + } + + private static byte[] AdjustQuickUnlockKey(ProtectedString QuickUnlockKey) + { + byte[] bUtf8 = QuickUnlockKey.ReadUtf8(); + SHA256Managed sha = new SHA256Managed(); + byte[] result = sha.ComputeHash(bUtf8); + MemUtil.ZeroByteArray(bUtf8); + return result; + } + } +} diff --git a/src/UnlockForm.Designer.cs b/src/UnlockForm.Designer.cs new file mode 100644 index 0000000..496784c --- /dev/null +++ b/src/UnlockForm.Designer.cs @@ -0,0 +1,127 @@ +using KeePass.UI; +namespace LockAssist +{ + partial class UnlockForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lLabel = new System.Windows.Forms.Label(); + this.bUnlock = new System.Windows.Forms.Button(); + this.bCancel = new System.Windows.Forms.Button(); + this.cbTogglePin = new System.Windows.Forms.CheckBox(); + this.stbPIN = new KeePass.UI.SecureTextBoxEx(); + this.SuspendLayout(); + // + // lLabel + // + this.lLabel.AutoSize = true; + this.lLabel.Location = new System.Drawing.Point(75, 67); + this.lLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.lLabel.Name = "lLabel"; + this.lLabel.Size = new System.Drawing.Size(243, 32); + this.lLabel.TabIndex = 1; + this.lLabel.Text = "Quick Unlock PIN:"; + // + // bUnlock + // + this.bUnlock.DialogResult = System.Windows.Forms.DialogResult.OK; + this.bUnlock.Location = new System.Drawing.Point(347, 181); + this.bUnlock.Margin = new System.Windows.Forms.Padding(5); + this.bUnlock.Name = "bUnlock"; + this.bUnlock.Size = new System.Drawing.Size(178, 55); + this.bUnlock.TabIndex = 2; + this.bUnlock.Text = "Unlock"; + this.bUnlock.UseVisualStyleBackColor = true; + // + // bCancel + // + this.bCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.bCancel.Location = new System.Drawing.Point(535, 181); + this.bCancel.Margin = new System.Windows.Forms.Padding(5); + this.bCancel.Name = "bCancel"; + this.bCancel.Size = new System.Drawing.Size(178, 55); + this.bCancel.TabIndex = 3; + this.bCancel.Text = "Cancel"; + this.bCancel.UseVisualStyleBackColor = true; + // + // cbTogglePin + // + this.cbTogglePin.Appearance = System.Windows.Forms.Appearance.Button; + this.cbTogglePin.AutoSize = true; + this.cbTogglePin.CheckAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cbTogglePin.Checked = true; + this.cbTogglePin.CheckState = System.Windows.Forms.CheckState.Checked; + this.cbTogglePin.Location = new System.Drawing.Point(647, 59); + this.cbTogglePin.Margin = new System.Windows.Forms.Padding(5); + this.cbTogglePin.Name = "cbTogglePin"; + this.cbTogglePin.Size = new System.Drawing.Size(58, 42); + this.cbTogglePin.TabIndex = 1; + this.cbTogglePin.Text = "***"; + this.cbTogglePin.UseVisualStyleBackColor = true; + this.cbTogglePin.CheckedChanged += new System.EventHandler(this.togglePIN_CheckedChanged); + // + // stbPIN + // + this.stbPIN.Location = new System.Drawing.Point(350, 62); + this.stbPIN.Margin = new System.Windows.Forms.Padding(5); + this.stbPIN.Name = "stbPIN"; + this.stbPIN.Size = new System.Drawing.Size(272, 38); + this.stbPIN.TabIndex = 0; + // + // UnlockForm + // + this.AcceptButton = this.bUnlock; + this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.bCancel; + this.ClientSize = new System.Drawing.Size(734, 290); + this.Controls.Add(this.cbTogglePin); + this.Controls.Add(this.bCancel); + this.Controls.Add(this.bUnlock); + this.Controls.Add(this.lLabel); + this.Controls.Add(this.stbPIN); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Margin = new System.Windows.Forms.Padding(5); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UnlockForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Quick Unlock"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private SecureTextBoxEx stbPIN; + private System.Windows.Forms.Label lLabel; + private System.Windows.Forms.Button bUnlock; + private System.Windows.Forms.Button bCancel; + private System.Windows.Forms.CheckBox cbTogglePin; + } +} \ No newline at end of file diff --git a/src/UnlockForm.cs b/src/UnlockForm.cs new file mode 100644 index 0000000..e392c18 --- /dev/null +++ b/src/UnlockForm.cs @@ -0,0 +1,44 @@ +using System.Windows.Forms; +using System.Drawing; +using KeePassLib.Security; + +using PluginTranslation; +using PluginTools; + +namespace LockAssist +{ + public partial class UnlockForm : Form + { + public UnlockForm() + { + InitializeComponent(); + cbTogglePin.Image = (Image)KeePass.Program.Resources.GetObject("B19x07_3BlackDots"); + if (cbTogglePin.Image != null) + { + cbTogglePin.AutoSize = false; + cbTogglePin.Text = string.Empty; + if (KeePass.UI.UIUtil.IsDarkTheme) + cbTogglePin.Image = KeePass.UI.UIUtil.InvertImage(cbTogglePin.Image); + } + + Text = QuickUnlockKeyProv.KeyProviderName; + lLabel.Text = PluginTranslate.UnlockLabel; + bUnlock.Text = PluginTranslate.ButtonUnlock; + bCancel.Text = KeePass.Resources.KPRes.Cancel; + + KeePass.UI.SecureTextBoxEx.InitEx(ref stbPIN); + cbTogglePin.Checked = true; + stbPIN.EnableProtection(cbTogglePin.Checked); + } + + public ProtectedString QuickUnlockKey + { + get { return stbPIN.TextEx; } + } + + private void togglePIN_CheckedChanged(object sender, System.EventArgs e) + { + stbPIN.EnableProtection(cbTogglePin.Checked); + } + } +} diff --git a/src/Utilities/Debug.cs b/src/Utilities/Debug.cs new file mode 100644 index 0000000..498a1d8 --- /dev/null +++ b/src/Utilities/Debug.cs @@ -0,0 +1,420 @@ +using KeePass.Forms; +using KeePass.UI; +using KeePassLib.Utility; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace PluginTools +{ + public static class PluginDebug + { + [Flags] + public enum LogLevelFlags + { + None = 0, + Info = 1, + Warning = 2, + Error = 4, + Success = 8, + All = Info | Warning | Error | Success + } + + public static string DebugFile { get; private set; } + public static LogLevelFlags LogLevel = LogLevelFlags.All; + + private static bool AutoSave = false; + private static bool AutoOpen = false; + private static bool AskOpen = true; + private static List m_DebugEntries = new List(); + private static string PluginName = string.Empty; + private static string PluginVersion; + private static bool m_DebugMode = false; + public static bool DebugMode + { + get { return m_DebugMode; } + set { m_DebugMode = value; } + } + private static Dictionary m_plugins = new Dictionary(); + public static Version DotNetVersion { get; private set; } + private static int m_DotNetRelease = 0; + + private static DateTime m_Start = DateTime.UtcNow; + + //Init + static PluginDebug() + { + PluginName = Assembly.GetExecutingAssembly().GetName().Name; + PluginVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + ulong uInst = KeePass.Util.WinUtil.GetMaxNetFrameworkVersion(); + DotNetVersion = new Version(StrUtil.VersionToString(uInst)); + try + { + RegistryKey rkRel = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", false); + m_DotNetRelease = (int)rkRel.GetValue("Release"); + if (rkRel != null) rkRel.Close(); + } + catch { } + + DebugFile = System.IO.Path.GetTempPath() + "Debug_" + PluginName + "_" + m_Start.ToString("yyyyMMddTHHmmssZ") + ".xml"; + + string level = KeePass.Program.CommandLineArgs["debuglevel"]; + if (string.IsNullOrEmpty(level)) + level = LogLevelFlags.All.ToString(); + try + { + LogLevel = (LogLevelFlags)Enum.Parse(LogLevel.GetType(), level); + } + catch { } + AutoSave = KeePass.Program.CommandLineArgs["debugautosave"] != null; + AutoOpen = KeePass.Program.CommandLineArgs["debugautoopen"] != null; + AskOpen = KeePass.Program.CommandLineArgs["debugsaveonly"] == null; + + DebugMode = KeePass.Program.CommandLineArgs[KeePass.App.AppDefs.CommandLineOptions.Debug] != null; + if (!DebugMode) + { + try + { + string[] plugins = KeePass.Program.CommandLineArgs["debugplugin"].ToLowerInvariant().Split(new char[] { ',' }); + DebugMode |= Array.Find(plugins, x => x.Trim() == PluginName.ToLowerInvariant()) != null; + DebugMode |= Array.Find(plugins, x => x.Trim() == "all") != null; + } + catch { } + } + KeePass.Program.MainForm.FormLoadPost += LoadPluginNames; + if (AutoSave) + AddInfo("AutoSave mode active", 0); + } + + #region Handle debug messages + public static void AddInfo(string msg) + { + AddMessage(LogLevelFlags.Info, msg, 5, null); + } + + public static void AddInfo(string msg, params string[] parameters) + { + AddMessage(LogLevelFlags.Info, msg, 5, parameters); + } + + public static void AddInfo(string msg, int CallstackFrames) + { + AddMessage(LogLevelFlags.Info, msg, CallstackFrames, null); + } + + public static void AddInfo(string msg, int CallstackFrames, params string[] parameters) + { + AddMessage(LogLevelFlags.Info, msg, CallstackFrames, parameters); + } + + public static void AddWarning(string msg) + { + AddMessage(LogLevelFlags.Warning, msg, 5, null); + } + + public static void AddWarning(string msg, params string[] parameters) + { + AddMessage(LogLevelFlags.Warning, msg, 5, parameters); + } + + public static void AddWarning(string msg, int CallstackFrames) + { + AddMessage(LogLevelFlags.Warning, msg, CallstackFrames, null); + } + + public static void AddWarning(string msg, int CallstackFrames, params string[] parameters) + { + AddMessage(LogLevelFlags.Warning, msg, CallstackFrames, parameters); + } + + public static void AddError(string msg) + { + AddMessage(LogLevelFlags.Error, msg, 5, null); + } + + public static void AddError(string msg, params string[] parameters) + { + AddMessage(LogLevelFlags.Error, msg, 5, parameters); + } + + public static void AddError(string msg, int CallstackFrames) + { + AddMessage(LogLevelFlags.Error, msg, CallstackFrames, null); + } + + public static void AddError(string msg, int CallstackFrames, params string[] parameters) + { + AddMessage(LogLevelFlags.Error, msg, CallstackFrames, parameters); + } + + public static void AddSuccess(string msg) + { + AddMessage(LogLevelFlags.Success, msg, 5, null); + } + + public static void AddSuccess(string msg, params string[] parameters) + { + AddMessage(LogLevelFlags.Success, msg, 5, parameters); + } + + public static void AddSuccess(string msg, int CallstackFrames) + { + AddMessage(LogLevelFlags.Success, msg, CallstackFrames, null); + } + + public static void AddSuccess(string msg, int CallstackFrames, params string[] parameters) + { + AddMessage(LogLevelFlags.Success, msg, CallstackFrames, parameters); + } + + private static void AddMessage(LogLevelFlags severity, string msg, int CallstackFrames, string[] parameters) + { + if (m_Saving || !DebugMode || ((severity & LogLevel) != severity)) return; + if (m_DebugEntries.Count > 0) + { + DebugEntry prev = m_DebugEntries[m_DebugEntries.Count - 1]; + if ((prev.severity == severity) && (prev.msg == msg) && ParamsEqual(prev.parameters, parameters)) + { + m_DebugEntries[m_DebugEntries.Count - 1].counter++; + return; + } + } + DebugEntry m = new DebugEntry(); + m.severity = severity; + m.msg = msg; + m.utc = DateTime.UtcNow; + m.counter = 1; + m.parameters = parameters; + if (CallstackFrames != 0) + { + System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(true); + for (int i = 0; i < st.FrameCount; i++) + { + if (m.sf.Count == CallstackFrames) break; + System.Diagnostics.StackFrame sf = st.GetFrame(i); + if (sf.GetMethod().DeclaringType.FullName != "PluginTools.PluginDebug") + m.sf.Add(sf); + } + } + m_DebugEntries.Add(m); + if (AutoSave) SaveDebugMessages(); + } + + private static bool ParamsEqual(string[] a, string[] b) + { + if ((a == null) && (b == null)) return true; + if ((a == null) && (b != null)) return false; + if ((a != null) && (b == null)) return false; + if (a.Length != b.Length) return false; + for (int i = 0; i < a.Length; i++) + if (a[i] != b[i]) return false; + return true; + } + + public static bool HasMessage(LogLevelFlags severity, string msg) + { + return m_DebugEntries.Find(x => (x.severity == severity) && (x.msg == msg)) != null; + } + #endregion + + public static void SaveOrShow() + { + if (m_DebugEntries.Count == 0) return; + SaveDebugMessages(); + if (AutoOpen || (AskOpen && Tools.AskYesNo("DebugFile: " + DebugFile + "\n\nOpen debug file?") == DialogResult.Yes)) + { + try + { + System.Diagnostics.Process.Start(DebugFile); + } + catch + { + if (KeePassLib.Native.NativeLib.IsUnix()) //The above is broken on mono + { + System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(); + psi.Arguments = DebugFile; + psi.FileName = "xdg-open"; + System.Diagnostics.Process.Start(psi); + } + } + } + } + + private static System.Xml.XmlWriter m_xw = null; + private static System.IO.StringWriter m_sw = null; + private static void StartXml() + { + m_sw = new System.IO.StringWriter(); + System.Xml.XmlWriterSettings ws = new System.Xml.XmlWriterSettings(); + ws.OmitXmlDeclaration = true; + ws.Indent = true; + ws.IndentChars = "\t"; + m_xw = System.Xml.XmlWriter.Create(m_sw, ws); + } + + private static string Xml + { + get + { + if (m_xw == null) return string.Empty; + m_sw.Flush(); + m_xw.Flush(); + string s = m_sw.ToString(); + m_xw = null; + m_sw = null; + return s; + } + } + + public static string DebugMessages + { + get + { + StartXml(); + LoadPluginNames(null, null); + string sEncoding = "\n"; + m_xw.WriteStartElement("DebugInfo"); + #region General info + m_xw.WriteStartElement("General"); + m_xw.WriteStartElement("Plugin"); + m_xw.WriteElementString("PluginName", PluginName); + m_xw.WriteElementString("PluginVersion", PluginVersion); + m_xw.WriteEndElement(); + m_xw.WriteStartElement("DebugTime"); + m_xw.WriteElementString("DebugStart", m_Start.ToString("yyyyMMddTHHmmssZ")); + m_xw.WriteElementString("DebugEnd", DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ")); + m_xw.WriteEndElement(); + m_xw.WriteElementString("LogLevel", LogLevel.ToString()); + + #region Add OS info + string os = string.Empty; + if (KeePass.Util.WinUtil.IsWindows9x) + os = "Windows 9x"; + else if (KeePass.Util.WinUtil.IsWindows2000) + os = "Windows 2000"; + else if (KeePass.Util.WinUtil.IsWindowsXP) + os = "Windows XP"; + else + { + if (KeePass.Util.WinUtil.IsAtLeastWindows10) + os = ">= Windows 10"; + else if (KeePass.Util.WinUtil.IsAtLeastWindows8) + os = ">= Windows 8"; + else if (KeePass.Util.WinUtil.IsAtLeastWindows7) + os = ">= Windows 7"; + else if (KeePass.Util.WinUtil.IsAtLeastWindowsVista) + os = ">= Windows Vista"; + else if (KeePass.Util.WinUtil.IsAtLeastWindows2000) + os = ">= Windows 2000"; + else os = "Unknown"; + } + if (KeePass.Util.WinUtil.IsAppX) + os += " (AppX)"; + os += " - " + Environment.OSVersion.ToString(); + m_xw.WriteElementString("OS", KeePass.Util.WinUtil.GetOSStr() + " " + os); + #endregion + m_xw.WriteElementString("DotNet", DotNetVersion.ToString() + (m_DotNetRelease > 0 ? " (" + m_DotNetRelease.ToString() + ")" : string.Empty)); + m_xw.WriteElementString("KeePass", Tools.KeePassVersion.ToString()); + + m_xw.WriteStartElement("LoadedPlugins"); + foreach (KeyValuePair kvp in m_plugins) + { + m_xw.WriteStartElement("Plugin"); + m_xw.WriteElementString("PluginName", kvp.Key); + m_xw.WriteElementString("PluginVersion", kvp.Value.ToString()); + m_xw.WriteEndElement(); + } + m_xw.WriteEndElement(); + m_xw.WriteEndElement(); + #endregion + + if (m_DebugEntries.Count == 0) + m_xw.WriteElementString("DebugMessages", null); + else + { + m_xw.WriteStartElement("DebugMessages"); + foreach (var m in m_DebugEntries) + m.GetXml(m_xw); + m_xw.WriteEndElement(); + } + + m_xw.WriteEndElement(); + return sEncoding + Xml; + } + } + + private static bool m_Saving = false; + public static void SaveDebugMessages() + { + if (m_Saving) return; + m_Saving = true; + try + { + System.IO.File.WriteAllText(DebugFile, DebugMessages); + } + catch (Exception ex) + { + Tools.ShowError("Can't save debug file: " + DebugFile + "\n\n" + ex.Message); + } + m_Saving = false; + } + + private static bool m_bAllPluginsLoaded = false; + private static void LoadPluginNames(object sender, EventArgs e) + { + if (m_bAllPluginsLoaded) return; + m_plugins = Tools.GetLoadedPluginsName(); + if (sender == null) return; + m_bAllPluginsLoaded = true; + KeePass.Program.MainForm.FormLoadPost -= LoadPluginNames; + } + + private class DebugEntry + { + public LogLevelFlags severity; + public string msg; + public DateTime utc; + public int counter; + public List sf = new List(); + public string[] parameters = null; + + public void GetXml(System.Xml.XmlWriter xw) + { + xw.WriteStartElement("DebugEntry"); + xw.WriteElementString("Message", msg); + xw.WriteElementString("Counter", counter.ToString()); + xw.WriteElementString("Severity", severity.ToString()); + xw.WriteElementString("DateTimeUtc", utc.ToString("yyyyMMddTHHmmssZ")); + if ((parameters == null) || parameters.Length == 0) + xw.WriteElementString("Parameters", null); + else + { + xw.WriteStartElement("Parameters"); + foreach (string p in parameters) + xw.WriteElementString("Param", p); + xw.WriteEndElement(); + } + if (sf.Count == 0) + xw.WriteElementString("StackFrames", null); + else + { + xw.WriteStartElement("StackFrames"); + foreach (var f in sf) + { + xw.WriteStartElement("StackFrame"); + xw.WriteElementString("Method", f.GetMethod().Name + " (" + f.GetMethod().DeclaringType.FullName + ")"); + xw.WriteElementString("FileName", System.IO.Path.GetFileName(f.GetFileName())); + xw.WriteElementString("Line", f.GetFileLineNumber().ToString()); + xw.WriteEndElement(); + } + xw.WriteEndElement(); + } + xw.WriteEndElement(); + } + } + } +} \ No newline at end of file diff --git a/src/Utilities/EventHelper.cs b/src/Utilities/EventHelper.cs new file mode 100644 index 0000000..c935249 --- /dev/null +++ b/src/Utilities/EventHelper.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Forms; + +namespace PluginTools +{ + public static class EventHelper + { + public static object DoInvoke(object sender, bool bUnwrapMonoWorkaround, List handlers, params object[] parameters) + { + if (handlers == null) return null; + List lDelegates = bUnwrapMonoWorkaround ? UnwrapMonoWorkaround(sender, handlers) : handlers; + if (lDelegates.Count == 1) + { + return lDelegates[0].DynamicInvoke(parameters) as object; + } + foreach (Delegate d in lDelegates) + d.DynamicInvoke(parameters); + return true; + } + + public static List GetEventHandlers(this object obj, string EventName) + { + List result = new List(); + if (obj == null) return result; + + Type t = obj.GetType(); + List event_fields = GetTypeEventFields(t); + EventHandlerList static_event_handlers = null; + + foreach (FieldInfo fi in event_fields) + { + if (!CheckEvent(fi, EventName)) continue; + + if (fi.IsStatic) + { + + if (static_event_handlers == null) + static_event_handlers = GetStaticEventHandlerList(t, obj); + + object idx = fi.GetValue(obj); + + + Delegate eh = static_event_handlers[idx]; + if (eh == null) + { + var head = GetHead(static_event_handlers); + List lDel = new List(); + CollectEventHandler(head, lDel); + if (lDel.Count == 0) continue; + result.AddRange(lDel); + } + else + { + Delegate[] dels = eh.GetInvocationList(); + if (dels == null) continue; + result.AddRange(dels); + } + } + else + { + EventInfo ei = t.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = t.GetEvent(EventName, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(EventName, AllBindings); + if (ei != null) + { + object val = fi.GetValue(obj); + Delegate mdel = (val as Delegate); + if (mdel != null) + result.AddRange(mdel.GetInvocationList()); + } + } + } + return result; + } + + public static void RemoveEventHandlers(this object obj, string EventName, List handlers) + { + if (obj == null) return; + if (handlers == null) return; + + Type t = obj.GetType(); + List event_fields = GetTypeEventFields(t); + + foreach (FieldInfo fi in event_fields) + { + if (!CheckEvent(fi, EventName)) continue; + + if (fi.IsStatic) + { + EventInfo ei = t.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = t.GetEvent(EventName, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(EventName, AllBindings); + if (ei == null) continue; + + foreach (Delegate del in handlers) + ei.RemoveEventHandler(obj, del); + } + else + { + EventInfo ei = t.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = t.GetEvent(EventName, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(EventName, AllBindings); + if (ei != null) + { + foreach (Delegate del in handlers) + ei.RemoveEventHandler(obj, del); + } + } + } + } + + public static bool AddEventHandlers(this object obj, string EventName, List handlers) + { + if (obj == null) return false; + if (handlers == null) return false; + + Type t = obj.GetType(); + List event_fields = GetTypeEventFields(t); + + bool added = false; + foreach (FieldInfo fi in event_fields) + { + if (!CheckEvent(fi, EventName)) continue; + + if (fi.IsStatic) + { + EventInfo ei = t.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = t.GetEvent(EventName, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(EventName, AllBindings); + if (ei == null) continue; + + foreach (var del in handlers) + ei.AddEventHandler(obj, del); + added = true; + } + else + { + EventInfo ei = t.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = t.GetEvent(EventName, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(fi.Name, AllBindings); + if (ei == null) + ei = fi.DeclaringType.GetEvent(EventName, AllBindings); + if (ei != null) + { + foreach (var del in handlers) + ei.AddEventHandler(obj, del); + added = true; + } + } + } + return added; + } + + private static Dictionary> m_dicEventFieldInfos = new Dictionary>(); + + private static BindingFlags AllBindings + { + get { return BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; } + } + + private static List GetTypeEventFields(Type t) + { + if (m_dicEventFieldInfos.ContainsKey(t)) return m_dicEventFieldInfos[t]; + + List lst = new List(); + BuildEventFields(t, lst); + m_dicEventFieldInfos[t] = lst; + return lst; + } + + private static void BuildEventFields(Type t, List lst) + { + // Type.GetEvent(s) gets all Events for the type AND it's ancestors + // Type.GetField(s) gets only Fields for the exact type. + // (BindingFlags.FlattenHierarchy only works on PROTECTED & PUBLIC + // doesn't work because Fieds are PRIVATE) + var eil = t.GetEvents(AllBindings); + lst.Clear(); + Dictionary> dBuffer = new Dictionary>(); + foreach (EventInfo ei in eil) + { + Type dt = ei.DeclaringType; + if (!dBuffer.ContainsKey(dt)) + { + dBuffer[dt] = new List(); + dBuffer[dt].AddRange(dt.GetFields(AllBindings)); + } + FieldInfo fi = t.GetField(ei.Name, AllBindings); + if (fi == null) + fi = dt.GetField(ei.Name, AllBindings); + if (fi == null) + fi = t.GetField("Event" + ei.Name, AllBindings); + if (fi == null) + fi = dt.GetField("Event" + ei.Name, AllBindings); + if (fi == null) + fi = t.GetField(ei.Name + "Event", AllBindings); + if (fi == null) + fi = dt.GetField(ei.Name + "Event", AllBindings); + if ((fi == null)) // && (dt.Name == "ListView")) + { + fi = dBuffer[dt].Find(x => x.Name.ToLowerInvariant() == "event_" + ei.Name.ToLowerInvariant()); + if (fi == null) fi = dBuffer[dt].Find(x => x.Name.ToLowerInvariant() == ei.Name.ToLowerInvariant() + "_event"); + } + if (fi != null) + lst.Add(fi); + } + } + + private static EventHandlerList GetStaticEventHandlerList(Type t, object obj) + { + MethodInfo mi = t.GetMethod("get_Events", AllBindings); + while ((mi == null) & (t.BaseType != null)) + { + t = t.BaseType; + mi = t.GetMethod("get_Events", AllBindings); + } + if (mi == null) return null; + return (EventHandlerList)mi.Invoke(obj, new object[] { }); + } + + private static bool CheckEvent(FieldInfo fi, string sEventName) + { + if (string.IsNullOrEmpty(sEventName)) + return false; + if (string.Compare(sEventName, fi.Name, true) == 0) + return true; + if (string.Compare("Event" + sEventName, fi.Name, true) == 0) + return true; + if (string.Compare(sEventName + "Event", fi.Name, true) == 0) + return true; + if (string.Compare("Event_" + sEventName, fi.Name, true) == 0) + return true; + if (string.Compare(sEventName + "_Event", fi.Name, true) == 0) + return true; + return false; + } + + private static void CollectEventHandler(object obj, List lDel) + { + try + { + if (obj == null) return; + FieldInfo fHandler = obj.GetType().GetField("handler", AllBindings); + if (fHandler == null) fHandler = obj.GetType().GetField("_handler", AllBindings); + if (fHandler == null) return; + Delegate d = (Delegate)fHandler.GetValue(obj); + Delegate[] d2 = d.GetInvocationList(); + lDel.AddRange(d2); + } + catch { } + } + + private static object GetHead(EventHandlerList eh) + { + try + { + FieldInfo fHead = eh.GetType().GetField("head", AllBindings); + if (fHead == null) fHead = eh.GetType().GetField("_head", AllBindings); + return fHead.GetValue(eh); + } + catch { } + return null; + } + + private static List UnwrapMonoWorkaround(object sender, List handlers) + { + if (!handlers[0].Method.DeclaringType.Name.Contains("MonoWorkaround")) + { + Tools.ShowInfo("No unwrapping required"); + return handlers; + } + List lHandlers = new List(); + FieldInfo fiHandlers = typeof(KeePassLib.Utility.MonoWorkarounds).GetField("m_dictHandlers", BindingFlags.Static | BindingFlags.NonPublic); + if (fiHandlers == null) + { + Tools.ShowError("No unwrapping possible - fiHandlers null"); + return handlers; + } + var dictHandler = fiHandlers.GetValue(null); + MethodInfo miTryGetValue = dictHandler.GetType().GetMethod("TryGetValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + object[] o = new object[] { sender, null }; + miTryGetValue.Invoke(dictHandler, o); + if ((o == null) || (o.Length < 1)) + { + Tools.ShowError("No unwrapping possible - object not found"); + return handlers; + } + Delegate dUnwrapped = Tools.GetField("m_fnOrg", o[1]) as Delegate; + Tools.ShowInfo("Wrapped\n\nOld: " + handlers[0].Method.Name + " - " + handlers[0].Method.DeclaringType.Name + "\n" + dUnwrapped.Method.Name + " - " + dUnwrapped.Method.DeclaringType.Name); + lHandlers.Add(dUnwrapped); + return lHandlers; + } + } + + public class MonoWorkaroundDialogResult : IDisposable + { + private bool m_bRequired = false; + private DialogResult m_dr = DialogResult.None; + + private object m_oMwaHandlerInfo = null; + private FieldInfo m_fiDialogResult = null; + + public MonoWorkaroundDialogResult(object sender) + { + if (sender == null) return; + if (!KeePassLib.Native.NativeLib.IsUnix()) return; + + GetDialogResultObject(sender); + + if (m_oMwaHandlerInfo == null) return; + + SetDialogResult(DialogResult.None); + } + + public void Dispose() + { + if (!m_bRequired) return; + SetDialogResult(m_dr); + } + + private void GetDialogResultObject(object sender) + { + if (sender == null) return; + + FieldInfo fiHandlers = typeof(KeePassLib.Utility.MonoWorkarounds).GetField("m_dictHandlers", BindingFlags.Static | BindingFlags.NonPublic); + object dictHandler = null; + if (fiHandlers != null) dictHandler = fiHandlers.GetValue(null); + + //Do NOT set bRequired, spmething went wrong and we will stick to KeePass standard behaviour + if (dictHandler == null) return; + + MethodInfo miTryGetValue = dictHandler.GetType().GetMethod("TryGetValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + //Do NOT set bRequired, spmething went wrong and we will stick to KeePass standard behaviour + if (miTryGetValue == null) return; + + object[] o = new object[] { sender, null }; + miTryGetValue.Invoke(dictHandler, o); + //Do NOT set bRequired, spmething went wrong and we will stick to KeePass standard behaviour + if ((o == null) || (o.Length < 1)) return; + + m_oMwaHandlerInfo = o[1]; + m_fiDialogResult = m_oMwaHandlerInfo.GetType().GetField("m_dr", BindingFlags.Instance | BindingFlags.NonPublic); + + m_bRequired = true; + m_dr = (DialogResult)m_fiDialogResult.GetValue(m_oMwaHandlerInfo); + } + + private void SetDialogResult(DialogResult dr) + { + m_fiDialogResult.SetValue(m_oMwaHandlerInfo, dr); + } + } +} diff --git a/src/Utilities/Tools_Controls.cs b/src/Utilities/Tools_Controls.cs new file mode 100644 index 0000000..8f432c6 --- /dev/null +++ b/src/Utilities/Tools_Controls.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Windows.Forms; + +namespace PluginTools +{ + public static partial class Tools + { + public static object GetField(string field, object obj) + { + BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; + return GetField(field, obj, bf); + } + + public static object GetField(string field, object obj, BindingFlags bf) + { + if (obj == null) return null; + FieldInfo fi = obj.GetType().GetField(field, bf); + if (fi == null) return null; + return fi.GetValue(obj); + } + + public static Control GetControl(string control) + { + return GetControl(control, KeePass.Program.MainForm); + } + + public static Control GetControl(string control, Control form) + { + if (form == null) return null; + if (string.IsNullOrEmpty(control)) return null; + Control[] cntrls = form.Controls.Find(control, true); + if (cntrls.Length == 0) return null; + return cntrls[0]; + } + + public static ToolStripMenuItem FindToolStripMenuItem(ToolStripItemCollection tsic, string key, bool searchAllChildren) + { + if (tsic == null) return null; + ToolStripItem[] tsi = FindToolStripMenuItems(tsic, key, searchAllChildren); + if (tsi.Length > 0) return tsi[0] as ToolStripMenuItem; + return null; + } + + public static ToolStripItem[] FindToolStripMenuItems(ToolStripItemCollection tsic, string key, bool searchAllChildren) + { + if (tsic == null) return new ToolStripItem[] { }; + ToolStripItem[] tsi = tsic.Find(key, searchAllChildren); + if (!MonoWorkaroundRequired || !searchAllChildren) return tsi; + + //Mono does not support 'searchAllChildren' for ToolStripItemCollection + //Iterate over all items and search for given item + List lItems = new List(tsi); + foreach (var item in tsic) + { + ToolStripMenuItem tsmi = item as ToolStripMenuItem; + if (tsmi == null) continue; + lItems.AddRange(FindToolStripMenuItems(tsmi.DropDownItems, key, searchAllChildren)); + } + return lItems.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Utilities/Tools_Main.cs b/src/Utilities/Tools_Main.cs new file mode 100644 index 0000000..67c123a --- /dev/null +++ b/src/Utilities/Tools_Main.cs @@ -0,0 +1,93 @@ +using KeePass.UI; +using System; +using System.Windows.Forms; + +namespace PluginTools +{ + public static partial class Tools + { + public static Version KeePassVersion { get; private set; } + public static string DefaultCaption = string.Empty; + public static string PluginURL = string.Empty; + public static string KeePassLanguageIso6391 { get; private set; } + + private static bool MonoWorkaroundRequired = KeePassLib.Native.NativeLib.IsUnix(); + + static Tools() + { + KeePass.UI.GlobalWindowManager.WindowAdded += OnWindowAdded; + KeePass.UI.GlobalWindowManager.WindowRemoved += OnWindowRemoved; + KeePassVersion = typeof(KeePass.Program).Assembly.GetName().Version; + KeePassLanguageIso6391 = KeePass.Program.Translation.Properties.Iso6391Code; + if (string.IsNullOrEmpty(KeePassLanguageIso6391)) KeePassLanguageIso6391 = "en"; + m_sPluginClassname = typeof(Tools).Assembly.GetName().Name + "Ext"; + } + + public static void OpenUrl(string sURL) + { + OpenUrl(sURL, null); + } + + public static void OpenUrl(string sURL, KeePassLib.PwEntry pe) + { + //Use KeePass built-in logic instead of System.Diagnostics.Process.Start + //For details see: https://sourceforge.net/p/keepass/discussion/329221/thread/f399b6d74b/#4801 + KeePass.Util.WinUtil.OpenUrl(sURL, pe, true); + } + + #region MessageBox shortcuts + public static DialogResult ShowError(string msg) + { + return ShowError(msg, DefaultCaption); + } + + public static DialogResult ShowInfo(string msg) + { + return ShowInfo(msg, DefaultCaption); + } + + public static DialogResult AskYesNo(string msg) + { + return AskYesNo(msg, DefaultCaption); + } + + public static DialogResult ShowError(string msg, string caption) + { + PluginDebug.AddError("Show error", 6, caption, msg); + return MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + public static DialogResult ShowInfo(string msg, string caption) + { + PluginDebug.AddInfo("Show info", 6, caption, msg); + return MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public static DialogResult AskYesNo(string msg, string caption) + { + DialogResult result = MessageBox.Show(msg, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + PluginDebug.AddInfo("Ask question", 6, caption, msg, "Result: " + result.ToString()); + return result; + } + #endregion + + #region GlobalWindowManager + public static void GlobalWindowManager(Form form) + { + if ((form == null) || (form.IsDisposed)) return; + form.Load += FormLoaded; + form.FormClosed += FormClosed; + } + + private static void FormLoaded(object sender, EventArgs e) + { + KeePass.UI.GlobalWindowManager.AddWindow(sender as Form, sender as IGwmWindow); + } + + private static void FormClosed(object sender, FormClosedEventArgs e) + { + KeePass.UI.GlobalWindowManager.RemoveWindow(sender as Form); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utilities/Tools_Options.cs b/src/Utilities/Tools_Options.cs new file mode 100644 index 0000000..51eb9fe --- /dev/null +++ b/src/Utilities/Tools_Options.cs @@ -0,0 +1,375 @@ +using KeePass.Forms; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace PluginTools +{ + public static partial class Tools + { + public static object GetPluginInstance(string PluginName) + { + string comp = PluginName + "." + PluginName + "Ext"; + BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; + try + { + var PluginManager = GetField("m_pluginManager", KeePass.Program.MainForm); + var PluginList = GetField("m_vPlugins", PluginManager); + MethodInfo IteratorMethod = PluginList.GetType().GetMethod("System.Collections.Generic.IEnumerable.GetEnumerator", bf); + IEnumerator PluginIterator = (IEnumerator)(IteratorMethod.Invoke(PluginList, null)); + while (PluginIterator.MoveNext()) + { + object result = GetField("m_pluginInterface", PluginIterator.Current); + if (comp == result.GetType().ToString()) return result; + } + } + + catch (Exception) { } + return null; + } + + public static Dictionary GetLoadedPluginsName() + { + Dictionary dPlugins = new Dictionary(); + BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; + try + { + var PluginManager = GetField("m_pluginManager", KeePass.Program.MainForm); + var PluginList = GetField("m_vPlugins", PluginManager); + MethodInfo IteratorMethod = PluginList.GetType().GetMethod("System.Collections.Generic.IEnumerable.GetEnumerator", bf); + IEnumerator PluginIterator = (IEnumerator)(IteratorMethod.Invoke(PluginList, null)); + while (PluginIterator.MoveNext()) + { + object result = GetField("m_pluginInterface", PluginIterator.Current); + var x = result.GetType().Assembly; + object[] v = x.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true); + Version ver = null; + if ((v != null) && (v.Length > 0)) + ver = new Version(((AssemblyFileVersionAttribute)v[0]).Version); + else + ver = result.GetType().Assembly.GetName().Version; + if (ver.Build < 0) ver = new Version(ver.Major, ver.Minor, 0, 0); + if (ver.Revision < 0) ver = new Version(ver.Major, ver.Minor, ver.Build, 0); + dPlugins[result.GetType().FullName] = ver; + } + } + catch (Exception) { } + return dPlugins; + } + + public static event EventHandler OptionsFormShown; + public static event EventHandler OptionsFormClosed; + + private static bool OptionsEnabled = (KeePass.Program.Config.UI.UIFlags & (ulong)KeePass.App.Configuration.AceUIFlags.DisableOptions) != (ulong)KeePass.App.Configuration.AceUIFlags.DisableOptions; + private static bool m_ActivatePluginTab = false; + private static string m_sPluginClassname = string.Empty; + private static OptionsForm m_of = null; + private const string c_tabRookiestyle = "m_tabRookiestyle"; + private const string c_tabControlRookiestyle = "m_tabControlRookiestyle"; + private static string m_TabPageName = string.Empty; + private static bool m_OptionsShown = false; + private static bool m_PluginContainerShown = false; + + public static void AddPluginToOptionsForm(KeePass.Plugins.Plugin p, UserControl uc) + { + m_OptionsShown = m_PluginContainerShown = false; + TabPage tPlugin = new TabPage(DefaultCaption); + tPlugin.CreateControl(); + tPlugin.Name = m_TabPageName = c_tabRookiestyle + p.GetType().Name; + uc.Dock = DockStyle.Fill; + uc.Padding = new Padding(15, 10, 15, 10); + tPlugin.Controls.Add(uc); + PluginDebug.AddInfo("Adding/Searching " + c_tabControlRookiestyle); + TabControl tcPlugins = AddPluginTabContainer(); + int i = 0; + bool insert = false; + for (int j = 0; j < tcPlugins.TabPages.Count; j++) + { + if (string.Compare(tPlugin.Text, tcPlugins.TabPages[j].Text, StringComparison.CurrentCultureIgnoreCase) < 0) + { + i = j; + insert = true; + break; + } + } + if (!insert) + { + i = tcPlugins.TabPages.Count; + PluginDebug.AddInfo(p.GetType().Name + " tab index : " + i.ToString() + " - insert!", 0); + } + else PluginDebug.AddInfo(p.GetType().Name + " tab index : " + i.ToString(), 0); + tcPlugins.TabPages.Insert(i, tPlugin); + AddPluginToOverview(tPlugin.Name.Replace(c_tabRookiestyle, string.Empty), tcPlugins); + if (p.SmallIcon != null) + { + tcPlugins.ImageList.Images.Add(tPlugin.Name, p.SmallIcon); + tPlugin.ImageKey = tPlugin.Name; + } + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + if (!string.IsNullOrEmpty(PluginURL)) AddPluginLink(uc); + } + + public static void AddPluginToOverview(string sPluginName) + { + AddPluginToOverview(sPluginName, null); + } + + private static void AddPluginToOverview(string sPluginName, TabControl tcPlugins) + { + if (tcPlugins == null) tcPlugins = AddPluginTabContainer(); + TabPage tpOverview = null; + ListView lv = null; + string sTabName = c_tabRookiestyle + "_PluginOverview"; + string sListViewName = c_tabRookiestyle + "_PluginOverviewListView"; + if (tcPlugins.TabPages.ContainsKey(sTabName)) + { + tpOverview = tcPlugins.TabPages[sTabName]; + lv = (ListView)tpOverview.Controls.Find(sListViewName, true)[0]; + PluginDebug.AddInfo("Found " + sTabName, 0, "Listview: " + (lv == null ? "null" : lv.Items.Count.ToString() + " /" + lv.Name.ToString())); + } + else + { + tpOverview = new TabPage("Overview"); + tpOverview.CreateControl(); + tpOverview.Name = sTabName; + UserControl uc = new UserControl(); + uc.Dock = DockStyle.Fill; + uc.Padding = new Padding(15, 10, 15, 10); + tpOverview.Controls.Add(uc); + lv = new ListView(); + lv.Name = sListViewName; + lv.Dock = DockStyle.Fill; + lv.View = View.Details; + lv.Columns.Add("Plugin"); + lv.Columns.Add("Version"); + lv.CheckBoxes = true; + tpOverview.Layout += TpOverview_Layout; + Label lInfo = new Label(); + lInfo.AutoSize = true; + lInfo.Text = "Use the checkbox to activate/deactivate debug mode"; + lInfo.Dock = DockStyle.Bottom; + uc.Controls.Add(lv); + uc.Controls.Add(lInfo); + } + lv.ItemCheck += Lv_ItemCheck; + lv.Sorting = SortOrder.Ascending; + lv.FullRowSelect = true; + ListViewItem lvi = new ListViewItem(); + lvi.Name = sPluginName; + lvi.Checked = PluginDebug.DebugMode; + lvi.Text = DefaultCaption; + Version v = new Version(0, 0); + GetLoadedPluginsName().TryGetValue(sPluginName.Replace("Ext", string.Empty) + "." + sPluginName, out v); + if (v == null) PluginDebug.AddError("Could not get loaded plugins' data", 0); + string ver = (v == null) ? "???" : v.ToString(); + if (ver.EndsWith(".0")) ver = ver.Substring(0, ver.Length - 2); + else ver += " (Dev)"; + lvi.SubItems.Add(ver); + lv.Items.Add(lvi); + tcPlugins.TabPages.Remove(tpOverview); + tcPlugins.TabPages.Add(tpOverview); + PluginDebug.AddInfo("Added " + sTabName, 0, "Listview: " + (lv == null ? "null" : lv.Items.Count.ToString() + " /" + lv.Name.ToString())); + } + + private static void TpOverview_Layout(object sender, LayoutEventArgs e) + { + string sListViewName = c_tabRookiestyle + "_PluginOverviewListView"; + ListView lv = (sender as TabPage).Controls.Find(sListViewName, true)[0] as ListView; + lv.BeginUpdate(); + lv.Columns[1].DisplayIndex = 0; + lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); + int w = lv.Columns[1].Width; + lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); + lv.Columns[1].Width = Math.Max(w, lv.Columns[1].Width); + lv.Columns[0].Width = lv.ClientSize.Width - lv.Columns[1].Width; + if (lv.Columns[0].Width < 150) + { + lv.Columns[1].Width = 100; + lv.Columns[0].Width = lv.ClientSize.Width - lv.Columns[1].Width; + } + lv.Columns[1].DisplayIndex = 1; + lv.EndUpdate(); + } + + private static void Lv_ItemCheck(object sender, ItemCheckEventArgs e) + { + ListViewItem lvi = (sender as ListView).Items[e.Index]; + if (lvi == null) return; + if (lvi.Text != DefaultCaption) return; + PluginDebug.DebugMode = e.NewValue == CheckState.Checked; + } + + private static void OnPluginTabsSelected(object sender, TabControlEventArgs e) + { + m_OptionsShown |= (e.TabPage.Name == m_TabPageName); + m_PluginContainerShown |= (m_OptionsShown || (e.TabPage.Name == c_tabRookiestyle)); + } + + public static UserControl GetPluginFromOptions(KeePass.Plugins.Plugin p, out bool PluginOptionsShown) + { + PluginOptionsShown = m_OptionsShown && m_PluginContainerShown; + TabPage tPlugin = Tools.GetControl(c_tabRookiestyle + p.GetType().Name, m_of) as TabPage; + if (tPlugin == null) return null; + return tPlugin.Controls[0] as UserControl; + } + + public static void ShowOptions() + { + m_ActivatePluginTab = true; + if (OptionsEnabled) + KeePass.Program.MainForm.ToolsMenu.DropDownItems["m_menuToolsOptions"].PerformClick(); + else + { + m_of = new OptionsForm(); + m_of.InitEx(KeePass.Program.MainForm.ClientIcons); + m_of.ShowDialog(); + } + } + + private static void AddPluginLink(UserControl uc) + { + LinkLabel llUrl = new LinkLabel(); + llUrl.Links.Add(0, PluginURL.Length, PluginURL); + llUrl.Text = PluginURL; + uc.Controls.Add(llUrl); + llUrl.Dock = DockStyle.Bottom; + llUrl.AutoSize = true; + llUrl.LinkClicked += new LinkLabelLinkClickedEventHandler(PluginURLClicked); + } + + private static void PluginURLClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + string target = e.Link.LinkData as string; + OpenUrl(target); + } + + private static void OnOptionsFormShown(object sender, EventArgs e) + { + m_of.Shown -= OnOptionsFormShown; + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + if (!tcMain.TabPages.ContainsKey(c_tabRookiestyle)) return; + TabPage tPlugins = tcMain.TabPages[c_tabRookiestyle]; + TabControl tcPlugins = Tools.GetControl(c_tabControlRookiestyle, tPlugins) as TabControl; + if (m_ActivatePluginTab) + { + tcMain.SelectedIndex = tcMain.TabPages.IndexOfKey(c_tabRookiestyle); + KeePass.Program.Config.Defaults.OptionsTabIndex = (uint)tcMain.SelectedIndex; + tcPlugins.SelectedIndex = tcPlugins.TabPages.IndexOfKey(c_tabRookiestyle + m_sPluginClassname); + } + m_ActivatePluginTab = false; + tcMain.Selected += OnPluginTabsSelected; + tcPlugins.Selected += OnPluginTabsSelected; + tcMain.ImageList.Images.Add(c_tabRookiestyle + "Icon", (Image)KeePass.Program.Resources.GetObject("B16x16_BlockDevice")); + tPlugins.ImageKey = c_tabRookiestyle + "Icon"; + m_PluginContainerShown |= tcMain.SelectedTab == tPlugins; + m_OptionsShown |= (tcPlugins.SelectedTab.Name == m_TabPageName); + CheckKeeTheme(tPlugins); + } + + private static void CheckKeeTheme(Control c) + { + Control check = GetControl("Rookiestyle_KeeTheme_Check", m_of); + if (check != null) return; + PluginDebug.AddInfo("Checking for KeeTheme"); + check = new Control(); + check.Name = "Rookiestyle_KeeTheme_Check"; + check.Visible = false; + m_of.Controls.Add(check); + KeePass.Plugins.Plugin p = (KeePass.Plugins.Plugin)GetPluginInstance("KeeTheme"); + if (p == null) return; + var t = GetField("_theme", p); + if (t == null) return; + bool bKeeThemeEnabled = (bool)t.GetType().GetProperty("Enabled").GetValue(t, null); + if (!bKeeThemeEnabled) return; + var v = GetField("_controlVisitor", p); + if (v == null) return; + MethodInfo miVisit = v.GetType().GetMethod("Visit", new Type[] { typeof(Control) }); + if (miVisit == null) return; + miVisit.Invoke(v, new object[] { c }); + } + + private static void OnWindowAdded(object sender, KeePass.UI.GwmWindowEventArgs e) + { + if (OptionsFormShown == null) return; + if (e.Form is OptionsForm) + { + m_of = e.Form as OptionsForm; + m_of.Shown += OnOptionsFormShown; + OptionsFormsEventArgs o = new OptionsFormsEventArgs(m_of); + OptionsFormShown(sender, o); + } + } + + private static void OnWindowRemoved(object sender, KeePass.UI.GwmWindowEventArgs e) + { + if (OptionsFormClosed == null) return; + if (e.Form is OptionsForm) + { + OptionsFormsEventArgs o = new OptionsFormsEventArgs(m_of); + OptionsFormClosed(sender, o); + } + } + + private static TabControl AddPluginTabContainer() + { + if (m_of == null) + { + PluginDebug.AddError("Could not identify KeePass options form", 0); + return null; + } + TabControl tcMain = Tools.GetControl("m_tabMain", m_of) as TabControl; + if (tcMain == null) + { + PluginDebug.AddError("Could not locate m_tabMain", 0); + return null; + } + TabPage tPlugins = null; + TabControl tcPlugins = null; + if (tcMain.TabPages.ContainsKey(c_tabRookiestyle)) + { + tPlugins = tcMain.TabPages[c_tabRookiestyle]; + tcPlugins = (TabControl)tPlugins.Controls[c_tabControlRookiestyle]; + if (tcPlugins == null) + { + PluginDebug.AddError("Could not locate " + c_tabControlRookiestyle, 0); + return null; + } + tcPlugins.Multiline = false; //Older version of PluginTools might still be used by other plugins + PluginDebug.AddInfo("Found " + c_tabControlRookiestyle, 0); + return tcPlugins; + } + tPlugins = new TabPage(KeePass.Resources.KPRes.Plugin + " " + m_of.Text); + tPlugins.Name = c_tabRookiestyle; + tPlugins.CreateControl(); + if (!OptionsEnabled) + { + while (tcMain.TabCount > 0) + tcMain.TabPages.RemoveAt(0); + PluginDebug.AddInfo("Removed tab pages from KeePass options form", 0); + } + tcMain.TabPages.Add(tPlugins); + tcPlugins = new TabControl(); + tcPlugins.Name = c_tabControlRookiestyle; + tcPlugins.Dock = DockStyle.Fill; + tcPlugins.Multiline = false; + tcPlugins.CreateControl(); + if (tcPlugins.ImageList == null) + tcPlugins.ImageList = new ImageList(); + tPlugins.Controls.Add(tcPlugins); + PluginDebug.AddInfo("Added " + c_tabControlRookiestyle, 0); + return tcPlugins; + } + + public class OptionsFormsEventArgs : EventArgs + { + public Form form; + + public OptionsFormsEventArgs(Form form) + { + this.form = form; + } + } + } +} \ No newline at end of file diff --git a/translationcopy.cmd b/translationcopy.cmd new file mode 100644 index 0000000..e7fcba3 --- /dev/null +++ b/translationcopy.cmd @@ -0,0 +1,10 @@ +@echo off +cd %~dp0 + +if "%1" == "Debug" ( + xcopy /y /c /i Translations\*.xml "..\_KeePass_Debug\Plugins\Translations" /EXCLUDE:..\translationsnocopy.txt +) +if "%1" == "ReleasePlgx" ( + xcopy /y /c /i Translations\*.xml "..\_KeePass_Release\Plugins\Translations" /EXCLUDE:..\translationsnocopy.txt + xcopy /y /c /i Translations\*.xml "..\_Releases\Translations" /EXCLUDE:..\translationsnocopy.txt +) \ No newline at end of file diff --git a/version.info b/version.info new file mode 100644 index 0000000..fa4d989 --- /dev/null +++ b/version.info @@ -0,0 +1,4 @@ +: +LockAssist:1.0 +LockAssist!de:1 +: \ No newline at end of file