1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.IO;
24 using System.Text;
25 using System.Windows.Forms;
26 
27 using KeePass.App;
28 using KeePass.Forms;
29 using KeePass.Resources;
30 using KeePass.UI;
31 using KeePass.Util;
32 
33 using KeePassLib;
34 using KeePassLib.Delegates;
35 using KeePassLib.Interfaces;
36 using KeePassLib.Keys;
37 using KeePassLib.Serialization;
38 using KeePassLib.Utility;
39 
40 using NativeLib = KeePassLib.Native.NativeLib;
41 
42 namespace KeePass.DataExchange
43 {
44 	public static class ExportUtil
45 	{
Export(PwExportInfo pwExportInfo, IStatusLogger slLogger)46 		public static bool Export(PwExportInfo pwExportInfo, IStatusLogger slLogger)
47 		{
48 			if(pwExportInfo == null) throw new ArgumentNullException("pwExportInfo");
49 			if(pwExportInfo.DataGroup == null) throw new ArgumentException();
50 
51 			if(!AppPolicy.Try(AppPolicyId.Export)) return false;
52 
53 			ExchangeDataForm dlg = new ExchangeDataForm();
54 			dlg.InitEx(true, pwExportInfo.ContextDatabase, pwExportInfo.DataGroup);
55 			dlg.ExportInfo = pwExportInfo;
56 
57 			bool bStatusActive = false;
58 
59 			try
60 			{
61 				if(dlg.ShowDialog() == DialogResult.OK)
62 				{
63 					FileFormatProvider ffp = dlg.ResultFormat;
64 					if(ffp == null) { Debug.Assert(false); return false; }
65 
66 					IOConnectionInfo ioc = null;
67 					if(ffp.RequiresFile)
68 					{
69 						string[] vFiles = dlg.ResultFiles;
70 						if(vFiles == null) { Debug.Assert(false); return false; }
71 						if(vFiles.Length == 0) { Debug.Assert(false); return false; }
72 						Debug.Assert(vFiles.Length == 1);
73 
74 						string strFile = vFiles[0];
75 						if(string.IsNullOrEmpty(strFile)) { Debug.Assert(false); return false; }
76 
77 						ioc = IOConnectionInfo.FromPath(strFile);
78 					}
79 
80 					if(slLogger != null)
81 					{
82 						slLogger.StartLogging(KPRes.ExportingStatusMsg, true);
83 						bStatusActive = true;
84 					}
85 
86 					Application.DoEvents(); // Redraw parent window
87 					return Export(pwExportInfo, ffp, ioc, slLogger);
88 				}
89 			}
90 			catch(Exception ex) { MessageService.ShowWarning(ex); }
91 			finally
92 			{
93 				UIUtil.DestroyForm(dlg);
94 				if(bStatusActive) slLogger.EndLogging();
95 			}
96 
97 			return false;
98 		}
99 
Export(PwExportInfo pwExportInfo, string strFormatName, IOConnectionInfo iocOutput)100 		public static bool Export(PwExportInfo pwExportInfo, string strFormatName,
101 			IOConnectionInfo iocOutput)
102 		{
103 			if(strFormatName == null) throw new ArgumentNullException("strFormatName");
104 			// iocOutput may be null
105 
106 			FileFormatProvider ffp = Program.FileFormatPool.Find(strFormatName);
107 			if(ffp == null) return false;
108 
109 			NullStatusLogger slLogger = new NullStatusLogger();
110 			return Export(pwExportInfo, ffp, iocOutput, slLogger);
111 		}
112 
Export(PwExportInfo pwExportInfo, FileFormatProvider fileFormat, IOConnectionInfo iocOutput, IStatusLogger slLogger)113 		public static bool Export(PwExportInfo pwExportInfo, FileFormatProvider fileFormat,
114 			IOConnectionInfo iocOutput, IStatusLogger slLogger)
115 		{
116 			if(pwExportInfo == null) throw new ArgumentNullException("pwExportInfo");
117 			if(pwExportInfo.DataGroup == null) throw new ArgumentException();
118 			if(fileFormat == null) throw new ArgumentNullException("fileFormat");
119 
120 			bool bFileReq = fileFormat.RequiresFile;
121 			if(bFileReq && (iocOutput == null))
122 				throw new ArgumentNullException("iocOutput");
123 			if(bFileReq && (iocOutput.Path.Length == 0))
124 				throw new ArgumentException();
125 
126 			PwDatabase pd = pwExportInfo.ContextDatabase;
127 			Debug.Assert(pd != null);
128 
129 			if(!AppPolicy.Try(AppPolicyId.Export)) return false;
130 			if(!AppPolicy.Current.ExportNoKey && (pd != null))
131 			{
132 				if(!KeyUtil.ReAskKey(pd, true)) return false;
133 			}
134 
135 			if(!fileFormat.SupportsExport) return false;
136 			if(!fileFormat.TryBeginExport()) return false;
137 
138 			CompositeKey ckOrgMasterKey = null;
139 			DateTime dtOrgMasterKey = PwDefs.DtDefaultNow;
140 
141 			PwGroup pgOrgData = pwExportInfo.DataGroup;
142 			PwGroup pgOrgRoot = ((pd != null) ? pd.RootGroup : null);
143 			bool bParentGroups = (pwExportInfo.ExportParentGroups && (pd != null) &&
144 				(pgOrgData != pgOrgRoot));
145 
146 			bool bExistedAlready = true; // No deletion by default
147 			bool bResult = false;
148 
149 			try
150 			{
151 				if(pwExportInfo.ExportMasterKeySpec && fileFormat.RequiresKey &&
152 					(pd != null))
153 				{
154 					KeyCreationForm kcf = new KeyCreationForm();
155 					kcf.InitEx((iocOutput ?? new IOConnectionInfo()), true);
156 
157 					if(UIUtil.ShowDialogNotValue(kcf, DialogResult.OK)) return false;
158 
159 					ckOrgMasterKey = pd.MasterKey;
160 					dtOrgMasterKey = pd.MasterKeyChanged;
161 
162 					pd.MasterKey = kcf.CompositeKey;
163 					pd.MasterKeyChanged = DateTime.UtcNow;
164 
165 					UIUtil.DestroyForm(kcf);
166 				}
167 
168 				if(bParentGroups)
169 				{
170 					PwGroup pgNew = WithParentGroups(pgOrgData, pd);
171 					pwExportInfo.DataGroup = pgNew;
172 					pd.RootGroup = pgNew;
173 				}
174 
175 				if(bFileReq) bExistedAlready = IOConnection.FileExists(iocOutput);
176 
177 				Stream s = (bFileReq ? IOConnection.OpenWrite(iocOutput) : null);
178 				try { bResult = fileFormat.Export(pwExportInfo, s, slLogger); }
179 				finally { if(s != null) s.Close(); }
180 
181 				if(bFileReq && bResult)
182 				{
183 					if(pwExportInfo.ExportPostOpen)
184 						NativeLib.StartProcess(iocOutput.Path);
185 					if(pwExportInfo.ExportPostShow)
186 						WinUtil.ShowFileInFileManager(iocOutput.Path, true);
187 				}
188 			}
189 			catch(Exception ex) { MessageService.ShowWarning(ex); }
190 			finally
191 			{
192 				if(ckOrgMasterKey != null)
193 				{
194 					pd.MasterKey = ckOrgMasterKey;
195 					pd.MasterKeyChanged = dtOrgMasterKey;
196 				}
197 
198 				if(bParentGroups)
199 				{
200 					pwExportInfo.DataGroup = pgOrgData;
201 					pd.RootGroup = pgOrgRoot;
202 				}
203 			}
204 
205 			if(bFileReq && !bResult && !bExistedAlready)
206 			{
207 				try { IOConnection.DeleteFile(iocOutput); }
208 				catch(Exception) { }
209 			}
210 
211 			return bResult;
212 		}
213 
WithParentGroups(PwGroup pg, PwDatabase pd)214 		private static PwGroup WithParentGroups(PwGroup pg, PwDatabase pd)
215 		{
216 			if(pg == null) { Debug.Assert(false); return null; }
217 			if(pd == null) { Debug.Assert(false); return pg; }
218 
219 			Dictionary<PwUuid, bool> dUuids = CollectUuids(pg);
220 
221 			PwGroup pgNew = FilterCloneGroup(pd.RootGroup, dUuids);
222 			Debug.Assert(pgNew.GetEntriesCount(true) == pg.GetEntriesCount(true));
223 			return pgNew;
224 		}
225 
CollectUuids(PwGroup pg)226 		private static Dictionary<PwUuid, bool> CollectUuids(PwGroup pg)
227 		{
228 			Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>();
229 
230 			Action<IStructureItem> fAdd = delegate(IStructureItem it)
231 			{
232 				if(it == null) { Debug.Assert(false); return; }
233 
234 				Debug.Assert(!d.ContainsKey(it.Uuid));
235 				d[it.Uuid] = true;
236 
237 				PwGroup pgParent = it.ParentGroup;
238 				while(pgParent != null)
239 				{
240 					d[pgParent.Uuid] = true;
241 					pgParent = pgParent.ParentGroup;
242 				}
243 			};
244 
245 			GroupHandler gh = delegate(PwGroup pgCur) { fAdd(pgCur); return true; };
246 			EntryHandler eh = delegate(PwEntry peCur) { fAdd(peCur); return true; };
247 
248 			fAdd(pg);
249 			pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
250 
251 			return d;
252 		}
253 
FilterCloneGroup(PwGroup pg, Dictionary<PwUuid, bool> dUuids)254 		private static PwGroup FilterCloneGroup(PwGroup pg, Dictionary<PwUuid, bool> dUuids)
255 		{
256 			PwGroup pgNew = new PwGroup();
257 			pgNew.Uuid = pg.Uuid;
258 			pgNew.AssignProperties(pg, false, true);
259 			Debug.Assert(pgNew.EqualsGroup(pg, (PwCompareOptions.IgnoreParentGroup |
260 				PwCompareOptions.PropertiesOnly), MemProtCmpMode.Full));
261 
262 			foreach(PwEntry pe in pg.Entries)
263 			{
264 				if(dUuids.ContainsKey(pe.Uuid))
265 					pgNew.AddEntry(pe.CloneDeep(), true, false);
266 			}
267 
268 			foreach(PwGroup pgSub in pg.Groups)
269 			{
270 				if(dUuids.ContainsKey(pgSub.Uuid))
271 					pgNew.AddGroup(FilterCloneGroup(pgSub, dUuids), true, false);
272 			}
273 
274 			return pgNew;
275 		}
276 	}
277 }
278