/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2012 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Xml; using System.Drawing; using KeePassLib.Collections; using KeePassLib.Interfaces; using KeePassLib.Security; using KeePassLib.Utility; namespace KeePassLib { /// /// A class representing a password entry. A password entry consists of several /// fields like title, user name, password, etc. Each password entry has a /// unique ID (UUID). /// public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable { private PwUuid m_uuid = PwUuid.Zero; private PwGroup m_pParentGroup = null; private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary(); private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary(); private AutoTypeConfig m_listAutoType = new AutoTypeConfig(); private PwObjectList m_listHistory = new PwObjectList(); private PwIcon m_pwIcon = PwIcon.Key; private PwUuid m_pwCustomIconID = PwUuid.Zero; private Color m_clrForeground = Color.Empty; private Color m_clrBackground = Color.Empty; private DateTime m_tCreation = PwDefs.DtDefaultNow; private DateTime m_tLastMod = PwDefs.DtDefaultNow; private DateTime m_tLastAccess = PwDefs.DtDefaultNow; private DateTime m_tExpire = PwDefs.DtDefaultNow; private bool m_bExpires = false; private ulong m_uUsageCount = 0; private string m_strOverrideUrl = string.Empty; private List m_vTags = new List(); /// /// UUID of this entry. /// public PwUuid Uuid { get { return m_uuid; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_uuid = value; } } /// /// Reference to a group which contains the current entry. /// public PwGroup ParentGroup { get { return m_pParentGroup; } /// Plugins: use PwGroup.AddEntry instead. internal set { m_pParentGroup = value; } } /// /// The date/time when the location of the object was last changed. /// public DateTime LocationChanged { get { return m_tParentGroupLastMod; } set { m_tParentGroupLastMod = value; } } /// /// Get or set all entry strings. /// public ProtectedStringDictionary Strings { get { return m_listStrings; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_listStrings = value; } } /// /// Get or set all entry binaries. /// public ProtectedBinaryDictionary Binaries { get { return m_listBinaries; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_listBinaries = value; } } /// /// Get or set all auto-type window/keystroke sequence associations. /// public AutoTypeConfig AutoType { get { return m_listAutoType; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_listAutoType = value; } } /// /// Get all previous versions of this entry (backups). /// public PwObjectList History { get { return m_listHistory; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_listHistory = value; } } /// /// Image ID specifying the icon that will be used for this entry. /// public PwIcon IconId { get { return m_pwIcon; } set { m_pwIcon = value; } } /// /// Get the custom icon ID. This value is 0, if no custom icon is /// being used (i.e. the icon specified by the IconID property /// should be displayed). /// public PwUuid CustomIconUuid { get { return m_pwCustomIconID; } set { Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); m_pwCustomIconID = value; } } /// /// Get or set the foreground color of this entry. /// public Color ForegroundColor { get { return m_clrForeground; } set { m_clrForeground = value; } } /// /// Get or set the background color of this entry. /// public Color BackgroundColor { get { return m_clrBackground; } set { m_clrBackground = value; } } /// /// The date/time when this entry was created. /// public DateTime CreationTime { get { return m_tCreation; } set { m_tCreation = value; } } /// /// The date/time when this entry was last accessed (read). /// public DateTime LastAccessTime { get { return m_tLastAccess; } set { m_tLastAccess = value; } } /// /// The date/time when this entry was last modified. /// public DateTime LastModificationTime { get { return m_tLastMod; } set { m_tLastMod = value; } } /// /// The date/time when this entry expires. Use the Expires property /// to specify if the entry does actually expire or not. /// public DateTime ExpiryTime { get { return m_tExpire; } set { m_tExpire = value; } } /// /// Specifies whether the entry expires or not. /// public bool Expires { get { return m_bExpires; } set { m_bExpires = value; } } /// /// Get or set the usage count of the entry. To increase the usage /// count by one, use the Touch function. /// public ulong UsageCount { get { return m_uUsageCount; } set { m_uUsageCount = value; } } /// /// Entry-specific override URL. If this string is non-empty, /// public string OverrideUrl { get { return m_strOverrideUrl; } set { if(value == null) throw new ArgumentNullException("value"); m_strOverrideUrl = value; } } /// /// List of tags associated with this entry. /// public List Tags { get { return m_vTags; } set { if(value == null) throw new ArgumentNullException("value"); m_vTags = value; } } public static EventHandler EntryTouched; public EventHandler Touched; /// /// Construct a new, empty password entry. Member variables will be initialized /// to their default values. /// /// If true, a new UUID will be created /// for this entry. If false, the UUID is zero and you must set it /// manually later. /// If true, the creation, last modification /// and last access times will be set to the current system time. public PwEntry(bool bCreateNewUuid, bool bSetTimes) { if(bCreateNewUuid) m_uuid = new PwUuid(true); if(bSetTimes) { m_tCreation = m_tLastMod = m_tLastAccess = m_tParentGroupLastMod = DateTime.Now; } } /// /// Construct a new, empty password entry. Member variables will be initialized /// to their default values. /// /// Reference to the containing group, this /// parameter may be null and set later manually. /// If true, a new UUID will be created /// for this entry. If false, the UUID is zero and you must set it /// manually later. /// If true, the creation, last modification /// and last access times will be set to the current system time. [Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")] public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes) { m_pParentGroup = pwParentGroup; if(bCreateNewUuid) m_uuid = new PwUuid(true); if(bSetTimes) { m_tCreation = m_tLastMod = m_tLastAccess = m_tParentGroupLastMod = DateTime.Now; } } /// /// Clone the current entry. The returned entry is an exact value copy /// of the current entry (including UUID and parent group reference). /// All mutable members are cloned. /// /// Exact value clone. All references to mutable values changed. public PwEntry CloneDeep() { PwEntry peNew = new PwEntry(false, false); peNew.m_uuid = m_uuid; // PwUuid is immutable peNew.m_pParentGroup = m_pParentGroup; peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; peNew.m_listStrings = m_listStrings.CloneDeep(); peNew.m_listBinaries = m_listBinaries.CloneDeep(); peNew.m_listAutoType = m_listAutoType.CloneDeep(); peNew.m_listHistory = m_listHistory.CloneDeep(); peNew.m_pwIcon = m_pwIcon; peNew.m_pwCustomIconID = m_pwCustomIconID; peNew.m_clrForeground = m_clrForeground; peNew.m_clrBackground = m_clrBackground; peNew.m_tCreation = m_tCreation; peNew.m_tLastMod = m_tLastMod; peNew.m_tLastAccess = m_tLastAccess; peNew.m_tExpire = m_tExpire; peNew.m_bExpires = m_bExpires; peNew.m_uUsageCount = m_uUsageCount; peNew.m_strOverrideUrl = m_strOverrideUrl; peNew.m_vTags = new List(m_vTags); return peNew; } public PwEntry CloneStructure() { PwEntry peNew = new PwEntry(false, false); peNew.m_uuid = m_uuid; // PwUuid is immutable peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; // Do not assign m_pParentGroup return peNew; } private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) { PwCompareOptions pwOpt = PwCompareOptions.None; if(bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup; if(bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod; if(bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess; if(bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory; if(bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup; return pwOpt; } [Obsolete] public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) { return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), MemProtCmpMode.None); } [Obsolete] public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, MemProtCmpMode mpCmpStr) { return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr); } public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, MemProtCmpMode mpCmpStr) { if(pe == null) { Debug.Assert(false); return false; } bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != PwCompareOptions.None); bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != PwCompareOptions.None); bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != PwCompareOptions.None); if(!m_uuid.EqualsValue(pe.m_uuid)) return false; if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) { if(m_pParentGroup != pe.m_pParentGroup) return false; if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) return false; } if(!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr)) return false; if(!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false; if(!m_listAutoType.Equals(pe.m_listAutoType)) return false; if((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) { bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != PwCompareOptions.None); if(!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount)) return false; if(bIgnoreLastBackup && (m_listHistory.UCount == 0)) { Debug.Assert(false); return false; } if(bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount)) return false; PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; if(bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd; if(bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; if(bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; for(uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist) { if(!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt( uHist), cmpSub, MemProtCmpMode.None)) return false; } } if(m_pwIcon != pe.m_pwIcon) return false; if(!m_pwCustomIconID.EqualsValue(pe.m_pwCustomIconID)) return false; if(m_clrForeground != pe.m_clrForeground) return false; if(m_clrBackground != pe.m_clrBackground) return false; if(m_tCreation != pe.m_tCreation) return false; if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false; if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false; if(m_tExpire != pe.m_tExpire) return false; if(m_bExpires != pe.m_bExpires) return false; if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; if(m_strOverrideUrl != pe.m_strOverrideUrl) return false; if(m_vTags.Count != pe.m_vTags.Count) return false; for(int iTag = 0; iTag < m_vTags.Count; ++iTag) { if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; } return true; } /// /// Assign properties to the current entry based on a template entry. /// /// Template entry. Must not be null. /// Only set the properties of the template entry /// if it is newer than the current one. /// If true, the history will be /// copied, too. /// If true, the /// LocationChanged property is copied, otherwise not. public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged) { Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); if(bOnlyIfNewer && (peTemplate.m_tLastMod < m_tLastMod)) return; // Template UUID should be the same as the current one Debug.Assert(m_uuid.EqualsValue(peTemplate.m_uuid)); m_uuid = peTemplate.m_uuid; if(bAssignLocationChanged) m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; m_listStrings = peTemplate.m_listStrings; m_listBinaries = peTemplate.m_listBinaries; m_listAutoType = peTemplate.m_listAutoType; if(bIncludeHistory) m_listHistory = peTemplate.m_listHistory; m_pwIcon = peTemplate.m_pwIcon; m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable m_clrForeground = peTemplate.m_clrForeground; m_clrBackground = peTemplate.m_clrBackground; m_tCreation = peTemplate.m_tCreation; m_tLastMod = peTemplate.m_tLastMod; m_tLastAccess = peTemplate.m_tLastAccess; m_tExpire = peTemplate.m_tExpire; m_bExpires = peTemplate.m_bExpires; m_uUsageCount = peTemplate.m_uUsageCount; m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_vTags = new List(peTemplate.m_vTags); } /// /// Touch the entry. This function updates the internal last access /// time. If the parameter is true, /// the last modification time gets updated, too. /// /// Modify last modification time. public void Touch(bool bModified) { Touch(bModified, true); } /// /// Touch the entry. This function updates the internal last access /// time. If the parameter is true, /// the last modification time gets updated, too. /// /// Modify last modification time. /// If true, all parent objects /// get touched, too. public void Touch(bool bModified, bool bTouchParents) { m_tLastAccess = DateTime.Now; ++m_uUsageCount; if(bModified) m_tLastMod = m_tLastAccess; if(this.Touched != null) this.Touched(this, new ObjectTouchedEventArgs(this, bModified, bTouchParents)); if(PwEntry.EntryTouched != null) PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this, bModified, bTouchParents)); if(bTouchParents && (m_pParentGroup != null)) m_pParentGroup.Touch(bModified, true); } /// /// Create a backup of this entry. The backup item doesn't contain any /// history items. /// [Obsolete] public void CreateBackup() { CreateBackup(null); } /// /// Create a backup of this entry. The backup item doesn't contain any /// history items. /// If this parameter isn't null, /// the history list is maintained automatically (i.e. old backups are /// deleted if there are too many or the history size is too large). /// This parameter may be null (no maintenance then). /// public void CreateBackup(PwDatabase pwHistMntcSettings) { PwEntry peCopy = CloneDeep(); peCopy.History = new PwObjectList(); // Remove history m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry if(pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); } /// /// Restore an entry snapshot from backups. /// /// Index of the backup item, to which /// should be reverted. [Obsolete] public void RestoreFromBackup(uint uBackupIndex) { RestoreFromBackup(uBackupIndex, null); } /// /// Restore an entry snapshot from backups. /// /// Index of the backup item, to which /// should be reverted. /// If this parameter isn't null, /// the history list is maintained automatically (i.e. old backups are /// deleted if there are too many or the history size is too large). /// This parameter may be null (no maintenance then). public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) { Debug.Assert(uBackupIndex < m_listHistory.UCount); if(uBackupIndex >= m_listHistory.UCount) throw new ArgumentOutOfRangeException("uBackupIndex"); PwEntry pe = m_listHistory.GetAt(uBackupIndex); Debug.Assert(pe != null); if(pe == null) throw new InvalidOperationException(); CreateBackup(pwHistMntcSettings); // Backup current data before restoring AssignProperties(pe, false, false, false); } public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, bool bIgnoreLastAccess) { if(peData == null) { Debug.Assert(false); return false; } PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd); if(bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; if(bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; foreach(PwEntry pe in m_listHistory) { if(pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; } return false; } /// /// Delete old history items if there are too many or the history /// size is too large. /// If one or more history items have been deleted, true /// is returned. Otherwise false. /// public bool MaintainBackups(PwDatabase pwSettings) { if(pwSettings == null) { Debug.Assert(false); return false; } bool bDeleted = false; int nMaxItems = pwSettings.HistoryMaxItems; if(nMaxItems >= 0) { while(m_listHistory.UCount > (uint)nMaxItems) { RemoveOldestBackup(); bDeleted = true; } } long lMaxSize = pwSettings.HistoryMaxSize; if(lMaxSize >= 0) { while(true) { ulong uHistSize = 0; foreach(PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); } if(uHistSize > (ulong)lMaxSize) { RemoveOldestBackup(); bDeleted = true; } else break; } } return bDeleted; } private void RemoveOldestBackup() { DateTime dtMin = DateTime.MaxValue; uint idxRemove = uint.MaxValue; for(uint u = 0; u < m_listHistory.UCount; ++u) { PwEntry pe = m_listHistory.GetAt(u); if(pe.LastModificationTime < dtMin) { idxRemove = u; dtMin = pe.LastModificationTime; } } if(idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove); } public bool GetAutoTypeEnabled() { if(!m_listAutoType.Enabled) return false; if(m_pParentGroup != null) return m_pParentGroup.GetAutoTypeEnabledInherited(); return PwGroup.DefaultAutoTypeEnabled; } public string GetAutoTypeSequence() { string strSeq = m_listAutoType.DefaultSequence; PwGroup pg = m_pParentGroup; while(pg != null) { if(strSeq.Length != 0) break; strSeq = pg.DefaultAutoTypeSequence; pg = pg.ParentGroup; } if(strSeq.Length != 0) return strSeq; if(PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan; return PwDefs.DefaultAutoTypeSequence; } public bool GetSearchingEnabled() { if(m_pParentGroup != null) return m_pParentGroup.GetSearchingEnabledInherited(); return PwGroup.DefaultSearchingEnabled; } /// /// Approximate the total size of this entry in bytes (including /// strings, binaries and history entries). /// /// Size in bytes. public ulong GetSize() { ulong uSize = 128; // Approx fixed length data foreach(KeyValuePair kvpStr in m_listStrings) { uSize += (ulong)kvpStr.Key.Length; uSize += (ulong)kvpStr.Value.Length; } foreach(KeyValuePair kvpBin in m_listBinaries) { uSize += (ulong)kvpBin.Key.Length; uSize += kvpBin.Value.Length; } uSize += (ulong)m_listAutoType.DefaultSequence.Length; foreach(AutoTypeAssociation a in m_listAutoType.Associations) { uSize += (ulong)a.WindowName.Length; uSize += (ulong)a.Sequence.Length; } foreach(PwEntry peHistory in m_listHistory) uSize += peHistory.GetSize(); uSize += (ulong)m_strOverrideUrl.Length; foreach(string strTag in m_vTags) uSize += (ulong)strTag.Length; return uSize; } public bool HasTag(string strTag) { if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } for(int i = 0; i < m_vTags.Count; ++i) { if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true; } return false; } public bool AddTag(string strTag) { if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } for(int i = 0; i < m_vTags.Count; ++i) { if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false; } m_vTags.Add(strTag); return true; } public bool RemoveTag(string strTag) { if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } for(int i = 0; i < m_vTags.Count; ++i) { if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) { m_vTags.RemoveAt(i); return true; } } return false; } public bool IsContainedIn(PwGroup pgContainer) { PwGroup pgCur = m_pParentGroup; while(pgCur != null) { if(pgCur == pgContainer) return true; pgCur = pgCur.ParentGroup; } return false; } public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids) { this.Uuid = pwNewUuid; if(bAlsoChangeHistoryUuids) { foreach(PwEntry peHist in m_listHistory) { peHist.Uuid = pwNewUuid; } } } } public sealed class PwEntryComparer : IComparer { private string m_strFieldName; private bool m_bCaseInsensitive; private bool m_bCompareNaturally; public PwEntryComparer(string strFieldName, bool bCaseInsensitive, bool bCompareNaturally) { if(strFieldName == null) throw new ArgumentNullException("strFieldName"); m_strFieldName = strFieldName; m_bCaseInsensitive = bCaseInsensitive; m_bCompareNaturally = bCompareNaturally; } public int Compare(PwEntry a, PwEntry b) { string strA = a.Strings.ReadSafe(m_strFieldName); string strB = b.Strings.ReadSafe(m_strFieldName); if(m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB); return string.Compare(strA, strB, m_bCaseInsensitive); } } }