-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
611 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using KeePass; | ||
using KeePass.Resources; | ||
using KeePassLib; | ||
using KeePassLib.Collections; | ||
using KeePassLib.Interfaces; | ||
using KeePassLib.Keys; | ||
using KeePassLib.Security; | ||
using KeePassLib.Serialization; | ||
using KeePassLib.Utility; | ||
|
||
namespace KeePassSubsetExport | ||
{ | ||
internal static class Exporter | ||
{ | ||
private static readonly IOConnectionInfo ConnectionInfo = new IOConnectionInfo(); | ||
|
||
/// <summary> | ||
/// Exports all entries with the given tag to a new database at the given path (multiple jobs possible). | ||
/// Each job is an entry in the "SubsetExportSettings" folder with a title "SubsetExport_*". | ||
/// Password == password field of the entry | ||
/// keyFilePath == "SubsetExport_KeyFilePath" string field on the entry | ||
/// targetFilePath == "SubsetExport_TargetFilePath" string field on the entry | ||
/// tag (filter) == "SubsetExport_Tag" string field on the entry | ||
/// </summary> | ||
/// <param name="sourceDb">The source database to run the exports on.</param> | ||
internal static void Export(PwDatabase sourceDb) | ||
{ | ||
// Get all entries out of the group "SubsetExportSettings" which start with "SubsetExport_" | ||
PwGroup settingsGroup = sourceDb.RootGroup.Groups.FirstOrDefault(g => g.Name == "SubsetExportSettings"); | ||
if (settingsGroup == null) | ||
{ | ||
return; | ||
} | ||
IEnumerable<PwEntry> jobSettings = settingsGroup.Entries | ||
.Where(x => x.Strings.ReadSafe("Title").Contains("SubsetExport_")); | ||
|
||
// Loop through all found entries - each on is a export job | ||
foreach (var settingsEntry in jobSettings) | ||
{ | ||
// Load settings for this job | ||
ProtectedString password = settingsEntry.Strings.GetSafe("Password"); | ||
string targetFilePath = settingsEntry.Strings.ReadSafe("SubsetExport_TargetFilePath"); | ||
string keyFilePath = settingsEntry.Strings.ReadSafe("SubsetExport_KeyFilePath"); | ||
string tag = settingsEntry.Strings.ReadSafe("SubsetExport_Tag"); | ||
|
||
// If a key file is given it must exist. | ||
if (!string.IsNullOrEmpty(keyFilePath) && !File.Exists(keyFilePath)) | ||
{ | ||
MessageService.ShowWarning("SubsetExport: Keyfile is given but could not be found for: " + | ||
settingsEntry.Strings.ReadSafe("Title"), keyFilePath); | ||
continue; | ||
} | ||
|
||
// Require at least targetFilePath, tag and at least one of password or keyFilePath. | ||
if (string.IsNullOrEmpty(targetFilePath) || string.IsNullOrEmpty(tag) || (password.IsEmpty && !File.Exists(keyFilePath))) | ||
{ | ||
MessageService.ShowWarning("SubsetExport: Missing settings for: " + | ||
settingsEntry.Strings.ReadSafe("Title")); | ||
continue; | ||
} | ||
|
||
try | ||
{ | ||
// Execute the export | ||
CopyToNewDb(sourceDb, targetFilePath, password, keyFilePath, tag); | ||
} | ||
catch (Exception e) | ||
{ | ||
MessageService.ShowWarning("SubsetExport failed:", e); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Exports all entries with the given tag to a new database at the given path. | ||
/// </summary> | ||
/// <param name="sourceDb">The source database.</param> | ||
/// <param name="targetFilePath">The path for the target database.</param> | ||
/// <param name="password">The password to protect the target database(optional if <para>keyFilePath</para> is set).</param> | ||
/// <param name="keyFilePath">The path to a key file to protect the target database (optional if <para>password</para> is set).</param> | ||
/// <param name="tag"></param> | ||
private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, ProtectedString password, string keyFilePath, string tag) | ||
{ | ||
// Create a key for the target database | ||
CompositeKey key = new CompositeKey(); | ||
|
||
bool hasPassword = false; | ||
bool hasKeyFile = false; | ||
|
||
if (!password.IsEmpty) | ||
{ | ||
byte[] passwordByteArray = password.ReadUtf8(); | ||
key.AddUserKey(new KcpPassword(passwordByteArray)); | ||
MemUtil.ZeroByteArray(passwordByteArray); | ||
hasPassword = true; | ||
} | ||
|
||
// Load a keyfile for the target database if requested (and add it to the key) | ||
if (!string.IsNullOrEmpty(keyFilePath)) | ||
{ | ||
bool bIsKeyProv = Program.KeyProviderPool.IsKeyProvider(keyFilePath); | ||
|
||
if (!bIsKeyProv) | ||
{ | ||
try | ||
{ | ||
key.AddUserKey(new KcpKeyFile(keyFilePath, true)); | ||
hasKeyFile = true; | ||
} | ||
catch (InvalidDataException exId) | ||
{ | ||
MessageService.ShowWarning(keyFilePath, exId); | ||
} | ||
catch (Exception exKf) | ||
{ | ||
MessageService.ShowWarning(keyFilePath, KPRes.KeyFileError, exKf); | ||
} | ||
} | ||
else | ||
{ | ||
KeyProviderQueryContext ctxKp = new KeyProviderQueryContext( | ||
ConnectionInfo, true, false); | ||
|
||
KeyProvider prov = Program.KeyProviderPool.Get(keyFilePath); | ||
bool bPerformHash = !prov.DirectKey; | ||
byte[] pbCustomKey = prov.GetKey(ctxKp); | ||
|
||
if ((pbCustomKey != null) && (pbCustomKey.Length > 0)) | ||
{ | ||
try | ||
{ | ||
key.AddUserKey(new KcpCustomKey(keyFilePath, pbCustomKey, bPerformHash)); | ||
hasKeyFile = true; | ||
} | ||
catch (Exception exCkp) | ||
{ | ||
MessageService.ShowWarning(exCkp); | ||
} | ||
|
||
MemUtil.ZeroByteArray(pbCustomKey); | ||
} | ||
} | ||
} | ||
|
||
// Check if at least a password or a keyfile have been added to the key object | ||
if (!hasPassword && !hasKeyFile) | ||
{ | ||
// Fail if not | ||
throw new InvalidOperationException("For the target database at least a password or a keyfile is required."); | ||
} | ||
|
||
// Create a new database | ||
PwDatabase targetDatabase = new PwDatabase(); | ||
|
||
// Apply the created key to the new database | ||
targetDatabase.New(new IOConnectionInfo(), key); | ||
|
||
// Copy database settings | ||
targetDatabase.Color = sourceDb.Color; | ||
targetDatabase.Compression = sourceDb.Compression; | ||
targetDatabase.DataCipherUuid = sourceDb.DataCipherUuid; | ||
targetDatabase.DefaultUserName = sourceDb.DefaultUserName; | ||
targetDatabase.Description = sourceDb.Description; | ||
targetDatabase.HistoryMaxItems = sourceDb.HistoryMaxItems; | ||
targetDatabase.HistoryMaxSize = sourceDb.HistoryMaxSize; | ||
targetDatabase.MaintenanceHistoryDays = sourceDb.MaintenanceHistoryDays; | ||
targetDatabase.MasterKeyChangeForce = sourceDb.MasterKeyChangeForce; | ||
targetDatabase.MasterKeyChangeRec = sourceDb.MasterKeyChangeRec; | ||
targetDatabase.Name = sourceDb.Name; | ||
targetDatabase.RecycleBinEnabled = sourceDb.RecycleBinEnabled; | ||
|
||
// Copy the root group name | ||
targetDatabase.RootGroup.Name = sourceDb.RootGroup.Name; | ||
|
||
// Find all entries matching the tag | ||
PwObjectList<PwEntry> entries = new PwObjectList<PwEntry>(); | ||
sourceDb.RootGroup.FindEntriesByTag(tag, entries, true); | ||
|
||
// Copy all entries to the new database | ||
foreach (PwEntry entry in entries) | ||
{ | ||
// Get or create the target group in the target database (including hierarchy) | ||
PwGroup targetGroup = CreateTargetGroupInDatebase(entry, targetDatabase); | ||
|
||
// Clone entry | ||
PwEntry peNew = new PwEntry(false, false); | ||
peNew.Uuid = entry.Uuid; | ||
peNew.AssignProperties(entry, false, true, true); | ||
|
||
// Add entry to the target group in the new database | ||
targetGroup.AddEntry(peNew, true); | ||
} | ||
|
||
// Create target folder (if not exist) | ||
string targetFolder = Path.GetDirectoryName(targetFilePath); | ||
|
||
if (targetFolder == null) | ||
{ | ||
throw new ArgumentException("Can't get target folder."); | ||
} | ||
Directory.CreateDirectory(targetFolder); | ||
|
||
// Save the new database under the target path | ||
KdbxFile kdbx = new KdbxFile(targetDatabase); | ||
|
||
using (FileStream outputStream = new FileStream(targetFilePath, FileMode.Create)) | ||
{ | ||
kdbx.Save(outputStream, null, KdbxFormat.Default, new NullStatusLogger()); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Get or create the target group of an entry in the target database (including hierarchy). | ||
/// </summary> | ||
/// <param name="entry">An entry wich is located in the folder with the target structure.</param> | ||
/// <param name="targetDatabase">The target database in which the folder structure should be created.</param> | ||
/// <returns>The target folder in the target database.</returns> | ||
private static PwGroup CreateTargetGroupInDatebase(PwEntry entry, PwDatabase targetDatabase) | ||
{ | ||
// Collect all group names from the entry up to the root group | ||
PwGroup group = entry.ParentGroup; | ||
List<string> list = new List<string>(); | ||
|
||
while (group != null) | ||
{ | ||
list.Add(group.Name); | ||
group = group.ParentGroup; | ||
} | ||
|
||
// Remove root group (we already changed the root group name) | ||
list.RemoveAt(list.Count - 1); | ||
// groups are in a bottom-up oder -> reverse to get top-down | ||
list.Reverse(); | ||
|
||
// Create a string representing the folder structure for FindCreateSubTree() | ||
string groupPath = string.Join("/", list.ToArray()); | ||
|
||
// Find the leaf folder or create it including hierarchical folder structure | ||
PwGroup targetGroup = targetDatabase.RootGroup.FindCreateSubTree(groupPath, new char[] | ||
{ | ||
'/' | ||
}); | ||
|
||
// Return the target folder (leaf folder) | ||
return targetGroup; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{2E861274-C7D4-4774-B9A0-A746E0774A88}</ProjectGuid> | ||
<OutputType>Library</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>KeePassSubsetExport</RootNamespace> | ||
<AssemblyName>KeePassSubsetExport</AssemblyName> | ||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> | ||
<FileAlignment>512</FileAlignment> | ||
<TargetFrameworkProfile /> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
<Prefer32Bit>false</Prefer32Bit> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
<Prefer32Bit>false</Prefer32Bit> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="KeePass"> | ||
<HintPath>..\Keepass\KeePass.exe</HintPath> | ||
</Reference> | ||
<Reference Include="System" /> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Drawing" /> | ||
<Reference Include="System.Windows.Forms" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="Exporter.cs" /> | ||
<Compile Include="KeePassSubsetExportExt.cs" /> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
<Compile Include="Properties\Resources.Designer.cs"> | ||
<AutoGen>True</AutoGen> | ||
<DesignTime>True</DesignTime> | ||
<DependentUpon>Resources.resx</DependentUpon> | ||
</Compile> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<EmbeddedResource Include="Properties\Resources.resx"> | ||
<Generator>ResXFileCodeGenerator</Generator> | ||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||
</EmbeddedResource> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="keepass.version" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="Resources\Key.png" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||
Other similar extension points exist, see Microsoft.Common.targets. | ||
<Target Name="BeforeBuild"> | ||
</Target> | ||
<Target Name="AfterBuild"> | ||
</Target> | ||
--> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 14 | ||
VisualStudioVersion = 14.0.25420.1 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{98A5390F-0320-4D88-AA77-C1154DD08838}") = "KeePassSubsetExport", "KeePassSubsetExport.csproj", "{2E861274-C7D4-4774-B9A0-A746E0774A88}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{2E861274-C7D4-4774-B9A0-A746E0774A88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{2E861274-C7D4-4774-B9A0-A746E0774A88}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{2E861274-C7D4-4774-B9A0-A746E0774A88}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{2E861274-C7D4-4774-B9A0-A746E0774A88}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System.Drawing; | ||
using KeePass.Forms; | ||
using KeePass.Plugins; | ||
|
||
namespace KeePassSubsetExport | ||
{ | ||
public class KeePassSubsetExportExt : Plugin | ||
{ | ||
private IPluginHost _host = null; | ||
|
||
public override Image SmallIcon | ||
{ | ||
get { return Properties.Resources.Key; } | ||
} | ||
|
||
public override string UpdateUrl | ||
{ | ||
get { return "https://github.com/lukeIam/KeePassSubsetExport/raw/master/keepass.version"; } | ||
} | ||
|
||
public override bool Initialize(IPluginHost host) | ||
{ | ||
_host = host; | ||
|
||
_host.MainWindow.FileSaved += StartExport; | ||
|
||
return true; | ||
} | ||
|
||
private void StartExport(object sender, FileSavedEventArgs args) | ||
{ | ||
Exporter.Export(args.Database); | ||
} | ||
|
||
public override void Terminate() | ||
{ | ||
if (_host != null) | ||
{ | ||
_host.MainWindow.FileSaved -= StartExport; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.