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