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.Drawing;
24 using System.IO;
25 using System.Text;
26 using System.Xml;
27 
28 using KeePass.Resources;
29 using KeePass.Util;
30 
31 using KeePassLib;
32 using KeePassLib.Interfaces;
33 using KeePassLib.Security;
34 using KeePassLib.Utility;
35 
36 namespace KeePass.DataExchange.Formats
37 {
38 	// 3.02-3.30+
39 	internal sealed class PwSafeXml302 : FileFormatProvider
40 	{
41 		private const string AttribLineBreak = "delimiter";
42 
43 		private const string ElemEntry = "entry";
44 		private const string ElemGroup = "group";
45 		private const string ElemTitle = "title";
46 		private const string ElemUserName = "username";
47 		private const string ElemPassword = "password";
48 		private const string ElemURL = "url";
49 		private const string ElemNotes = "notes";
50 		private const string ElemEMail = "email";
51 
52 		private const string ElemAutoType = "autotype";
53 		private const string ElemRunCommand = "runcommand";
54 
55 		private const string ElemCreationTime = "ctime";
56 		private const string ElemLastAccessTime = "atime";
57 		private const string ElemExpireTime = "ltime";
58 		private const string ElemLastModTime = "pmtime";
59 		private const string ElemRecordModTime = "rmtime";
60 		private const string ElemCreationTimeX = "ctimex";
61 		private const string ElemLastAccessTimeX = "atimex";
62 		private const string ElemExpireTimeX = "xtimex"; // Yes, inconsistent
63 		private const string ElemLastModTimeX = "pmtimex";
64 		private const string ElemRecordModTimeX = "rmtimex";
65 
66 		private const string ElemEntryHistory = "pwhistory";
67 		private const string ElemEntryHistoryContainer = "history_entries";
68 		private const string ElemEntryHistoryItem = "history_entry";
69 		private const string ElemEntryHistoryItemTime = "changed";
70 		private const string ElemEntryHistoryItemTimeX = "changedx";
71 		private const string ElemEntryHistoryItemPassword = "oldpassword";
72 
73 		private const string ElemTimePartDate = "date";
74 		private const string ElemTimePartTime = "time";
75 
76 		private const string XPathUseDefaultUser = "Preferences/UseDefaultUser";
77 		private const string XPathDefaultUser = "Preferences/DefaultUsername";
78 
79 		public override bool SupportsImport { get { return true; } }
80 		public override bool SupportsExport { get { return false; } }
81 
82 		public override string FormatName { get { return "Password Safe XML"; } }
83 		public override string DefaultExtension { get { return "xml"; } }
84 		public override string ApplicationGroup { get { return KPRes.PasswordManagers; } }
85 
86 		public override Image SmallIcon
87 		{
88 			get { return KeePass.Properties.Resources.B16x16_Imp_PwSafe; }
89 		}
90 
91 		private sealed class DatePasswordPair
92 		{
93 			public DateTime Time = DateTime.UtcNow;
94 			public string Password = string.Empty;
95 		}
96 
Import(PwDatabase pwStorage, Stream sInput, IStatusLogger slLogger)97 		public override void Import(PwDatabase pwStorage, Stream sInput,
98 			IStatusLogger slLogger)
99 		{
100 			byte[] pbData = MemUtil.Read(sInput);
101 
102 			try
103 			{
104 				string strData = StrUtil.Utf8.GetString(pbData);
105 				if(strData.StartsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
106 					StrUtil.CaseIgnoreCmp) && (strData.IndexOf(
107 					"WhatSaved=\"Password Safe V3.29\"", StrUtil.CaseIgnoreCmp) >= 0))
108 				{
109 					// Fix broken XML exported by Password Safe 3.29;
110 					// this has been fixed in 3.30
111 					strData = strData.Replace("<DefaultUsername<![CDATA[",
112 						"<DefaultUsername><![CDATA[");
113 					strData = strData.Replace("<DefaultSymbols<![CDATA[",
114 						"<DefaultSymbols><![CDATA[");
115 
116 					pbData = StrUtil.Utf8.GetBytes(strData);
117 				}
118 			}
119 			catch(Exception) { Debug.Assert(false); }
120 
121 			XmlDocument xmlDoc = XmlUtilEx.CreateXmlDocument();
122 			using(MemoryStream ms = new MemoryStream(pbData, false))
123 			{
124 				xmlDoc.Load(ms);
125 			}
126 
127 			XmlNode xmlRoot = xmlDoc.DocumentElement;
128 
129 			string strLineBreak = "\n";
130 			try
131 			{
132 				XmlAttributeCollection xac = xmlRoot.Attributes;
133 				XmlNode xmlBreak = xac.GetNamedItem(AttribLineBreak);
134 				string strBreak = xmlBreak.Value;
135 
136 				if(!string.IsNullOrEmpty(strBreak))
137 					strLineBreak = strBreak;
138 				else { Debug.Assert(false); }
139 			}
140 			catch(Exception) { Debug.Assert(false); }
141 
142 			foreach(XmlNode xmlChild in xmlRoot.ChildNodes)
143 			{
144 				if(xmlChild.Name == ElemEntry)
145 					ImportEntry(xmlChild, pwStorage, strLineBreak);
146 			}
147 
148 			XmlNode xnUse = xmlRoot.SelectSingleNode(XPathUseDefaultUser);
149 			if(xnUse != null)
150 			{
151 				string strUse = XmlUtil.SafeInnerText(xnUse);
152 				if(StrUtil.StringToBool(strUse))
153 				{
154 					XmlNode xn = xmlRoot.SelectSingleNode(XPathDefaultUser);
155 					if((xn != null) && (pwStorage.DefaultUserName.Length == 0))
156 					{
157 						pwStorage.DefaultUserName = XmlUtil.SafeInnerText(xn);
158 						if(pwStorage.DefaultUserName.Length != 0)
159 							pwStorage.DefaultUserNameChanged = DateTime.UtcNow;
160 					}
161 				}
162 			}
163 		}
164 
ImportEntry(XmlNode xmlNode, PwDatabase pwStorage, string strLineBreak)165 		private static void ImportEntry(XmlNode xmlNode, PwDatabase pwStorage,
166 			string strLineBreak)
167 		{
168 			Debug.Assert(xmlNode != null); if(xmlNode == null) return;
169 
170 			PwEntry pe = new PwEntry(true, true);
171 			string strGroupName = string.Empty;
172 
173 			List<DatePasswordPair> listHistory = null;
174 
175 			foreach(XmlNode xmlChild in xmlNode.ChildNodes)
176 			{
177 				if(xmlChild.Name == ElemGroup)
178 					strGroupName = XmlUtil.SafeInnerText(xmlChild);
179 				else if(xmlChild.Name == ElemTitle)
180 					pe.Strings.Set(PwDefs.TitleField,
181 						new ProtectedString(pwStorage.MemoryProtection.ProtectTitle,
182 						XmlUtil.SafeInnerText(xmlChild)));
183 				else if(xmlChild.Name == ElemUserName)
184 					pe.Strings.Set(PwDefs.UserNameField,
185 						new ProtectedString(pwStorage.MemoryProtection.ProtectUserName,
186 						XmlUtil.SafeInnerText(xmlChild)));
187 				else if(xmlChild.Name == ElemPassword)
188 					pe.Strings.Set(PwDefs.PasswordField,
189 						new ProtectedString(pwStorage.MemoryProtection.ProtectPassword,
190 						XmlUtil.SafeInnerText(xmlChild)));
191 				else if(xmlChild.Name == ElemURL)
192 					pe.Strings.Set(PwDefs.UrlField,
193 						new ProtectedString(pwStorage.MemoryProtection.ProtectUrl,
194 						XmlUtil.SafeInnerText(xmlChild)));
195 				else if(xmlChild.Name == ElemNotes)
196 					pe.Strings.Set(PwDefs.NotesField,
197 						new ProtectedString(pwStorage.MemoryProtection.ProtectNotes,
198 						XmlUtil.SafeInnerText(xmlChild, strLineBreak)));
199 				else if(xmlChild.Name == ElemEMail)
200 					pe.Strings.Set("E-Mail", new ProtectedString(false,
201 						XmlUtil.SafeInnerText(xmlChild)));
202 				else if(xmlChild.Name == ElemCreationTime)
203 					pe.CreationTime = ReadDateTime(xmlChild);
204 				else if(xmlChild.Name == ElemLastAccessTime)
205 					pe.LastAccessTime = ReadDateTime(xmlChild);
206 				else if(xmlChild.Name == ElemExpireTime)
207 				{
208 					pe.ExpiryTime = ReadDateTime(xmlChild);
209 					pe.Expires = true;
210 				}
211 				else if(xmlChild.Name == ElemLastModTime) // = last mod
212 					pe.LastModificationTime = ReadDateTime(xmlChild);
213 				else if(xmlChild.Name == ElemRecordModTime) // = last mod
214 					pe.LastModificationTime = ReadDateTime(xmlChild);
215 				else if(xmlChild.Name == ElemCreationTimeX)
216 					pe.CreationTime = ReadDateTimeX(xmlChild);
217 				else if(xmlChild.Name == ElemLastAccessTimeX)
218 					pe.LastAccessTime = ReadDateTimeX(xmlChild);
219 				else if(xmlChild.Name == ElemExpireTimeX)
220 				{
221 					pe.ExpiryTime = ReadDateTimeX(xmlChild);
222 					pe.Expires = true;
223 				}
224 				else if(xmlChild.Name == ElemLastModTimeX) // = last mod
225 					pe.LastModificationTime = ReadDateTimeX(xmlChild);
226 				else if(xmlChild.Name == ElemRecordModTimeX) // = last mod
227 					pe.LastModificationTime = ReadDateTimeX(xmlChild);
228 				else if(xmlChild.Name == ElemAutoType)
229 					pe.AutoType.DefaultSequence = XmlUtil.SafeInnerText(xmlChild);
230 				else if(xmlChild.Name == ElemRunCommand)
231 					pe.OverrideUrl = XmlUtil.SafeInnerText(xmlChild);
232 				else if(xmlChild.Name == ElemEntryHistory)
233 					listHistory = ReadEntryHistory(xmlChild);
234 			}
235 
236 			if(listHistory != null)
237 			{
238 				string strPassword = pe.Strings.ReadSafe(PwDefs.PasswordField);
239 				DateTime dtLastMod = pe.LastModificationTime;
240 
241 				foreach(DatePasswordPair dpp in listHistory)
242 				{
243 					pe.Strings.Set(PwDefs.PasswordField, new ProtectedString(
244 						pwStorage.MemoryProtection.ProtectPassword,
245 						dpp.Password));
246 					pe.LastModificationTime = dpp.Time;
247 
248 					pe.CreateBackup(null);
249 				}
250 				// Maintain backups manually now (backups from the imported file
251 				// might have been out of order)
252 				pe.MaintainBackups(pwStorage);
253 
254 				pe.Strings.Set(PwDefs.PasswordField, new ProtectedString(
255 					pwStorage.MemoryProtection.ProtectPassword,
256 					strPassword));
257 				pe.LastModificationTime = dtLastMod;
258 			}
259 
260 			PwGroup pgContainer = pwStorage.RootGroup;
261 			if(strGroupName.Length != 0)
262 				pgContainer = pwStorage.RootGroup.FindCreateSubTree(strGroupName,
263 					new string[1] { "." }, true);
264 			pgContainer.AddEntry(pe, true);
265 			pgContainer.IsExpanded = true;
266 		}
267 
ReadDateTime(XmlNode xmlNode)268 		private static DateTime ReadDateTime(XmlNode xmlNode)
269 		{
270 			Debug.Assert(xmlNode != null); if(xmlNode == null) return DateTime.UtcNow;
271 
272 			int[] vTimeParts = new int[6];
273 			DateTime dtTemp;
274 			foreach(XmlNode xmlChild in xmlNode.ChildNodes)
275 			{
276 				if(xmlChild.Name == ElemTimePartDate)
277 				{
278 					if(DateTime.TryParse(XmlUtil.SafeInnerText(xmlChild), out dtTemp))
279 					{
280 						vTimeParts[0] = dtTemp.Year;
281 						vTimeParts[1] = dtTemp.Month;
282 						vTimeParts[2] = dtTemp.Day;
283 					}
284 				}
285 				else if(xmlChild.Name == ElemTimePartTime)
286 				{
287 					if(DateTime.TryParse(XmlUtil.SafeInnerText(xmlChild), out dtTemp))
288 					{
289 						vTimeParts[3] = dtTemp.Hour;
290 						vTimeParts[4] = dtTemp.Minute;
291 						vTimeParts[5] = dtTemp.Second;
292 					}
293 				}
294 				else { Debug.Assert(false); }
295 			}
296 
297 			return (new DateTime(vTimeParts[0], vTimeParts[1], vTimeParts[2],
298 				vTimeParts[3], vTimeParts[4], vTimeParts[5],
299 				DateTimeKind.Local)).ToUniversalTime();
300 		}
301 
ReadDateTimeX(XmlNode xmlNode)302 		private static DateTime ReadDateTimeX(XmlNode xmlNode)
303 		{
304 			string strDate = XmlUtil.SafeInnerText(xmlNode);
305 
306 			DateTime dt;
307 			if(StrUtil.TryParseDateTime(strDate, out dt))
308 				return TimeUtil.ToUtc(dt, false);
309 
310 			Debug.Assert(false);
311 			return DateTime.UtcNow;
312 		}
313 
ReadEntryHistory(XmlNode xmlNode)314 		private static List<DatePasswordPair> ReadEntryHistory(XmlNode xmlNode)
315 		{
316 			List<DatePasswordPair> list = null;
317 
318 			foreach(XmlNode xmlChild in xmlNode)
319 			{
320 				if(xmlChild.Name == ElemEntryHistoryContainer)
321 					list = ReadEntryHistoryContainer(xmlChild);
322 			}
323 
324 			return list;
325 		}
326 
ReadEntryHistoryContainer(XmlNode xmlNode)327 		private static List<DatePasswordPair> ReadEntryHistoryContainer(XmlNode xmlNode)
328 		{
329 			List<DatePasswordPair> list = new List<DatePasswordPair>();
330 
331 			foreach(XmlNode xmlChild in xmlNode)
332 			{
333 				if(xmlChild.Name == ElemEntryHistoryItem)
334 					list.Add(ReadEntryHistoryItem(xmlChild));
335 			}
336 
337 			return list;
338 		}
339 
ReadEntryHistoryItem(XmlNode xmlNode)340 		private static DatePasswordPair ReadEntryHistoryItem(XmlNode xmlNode)
341 		{
342 			DatePasswordPair dpp = new DatePasswordPair();
343 
344 			foreach(XmlNode xmlChild in xmlNode)
345 			{
346 				if(xmlChild.Name == ElemEntryHistoryItemTime)
347 					dpp.Time = ReadDateTime(xmlChild);
348 				else if(xmlChild.Name == ElemEntryHistoryItemTimeX)
349 					dpp.Time = ReadDateTimeX(xmlChild);
350 				else if(xmlChild.Name == ElemEntryHistoryItemPassword)
351 					dpp.Password = XmlUtil.SafeInnerText(xmlChild);
352 			}
353 
354 			return dpp;
355 		}
356 	}
357 }
358