/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2017 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.IO; using System.Text; #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) using System.Security.AccessControl; #endif using ModernKeePassLib.Native; using ModernKeePassLib.Utility; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; using ModernKeePassLib.Resources; namespace ModernKeePassLib.Serialization { public sealed class FileTransactionEx { private bool m_bTransacted; private IOConnectionInfo m_iocBase; private IOConnectionInfo m_iocTemp; private bool m_bMadeUnhidden = false; private const string StrTempSuffix = ".tmp"; private static Dictionary g_dEnabled = new Dictionary(StrUtil.CaseIgnoreComparer); private static bool g_bExtraSafe = false; internal static bool ExtraSafe { get { return g_bExtraSafe; } set { g_bExtraSafe = value; } } public FileTransactionEx(IOConnectionInfo iocBaseFile) { Initialize(iocBaseFile, true); } public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted) { Initialize(iocBaseFile, bTransacted); } private void Initialize(IOConnectionInfo iocBaseFile, bool bTransacted) { if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile"); m_bTransacted = bTransacted; m_iocBase = iocBaseFile.CloneDeep(); string strPath = m_iocBase.Path; #if !ModernKeePassLib if(m_iocBase.IsLocalFile()) { try { if(File.Exists(strPath)) { // Symbolic links are realized via reparse points; // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365503.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365680.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365006.aspx // Performing a file transaction on a symbolic link // would delete/replace the symbolic link instead of // writing to its target FileAttributes fa = File.GetAttributes(strPath); if((long)(fa & FileAttributes.ReparsePoint) != 0) m_bTransacted = false; } } catch(Exception) { Debug.Assert(false); } } // Prevent transactions for FTP URLs under .NET 4.0 in order to // avoid/workaround .NET bug 621450: // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only if(strPath.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && (Environment.Version.Major >= 4) && !NativeLib.IsUnix()) m_bTransacted = false; #endif foreach(KeyValuePair kvp in g_dEnabled) { if(strPath.StartsWith(kvp.Key, StrUtil.CaseIgnoreCmp)) { m_bTransacted = kvp.Value; break; } } if(m_bTransacted) { m_iocTemp = m_iocBase.CloneDeep(); m_iocTemp.Path += StrTempSuffix; } else m_iocTemp = m_iocBase; } public Stream OpenWrite() { if(!m_bTransacted) m_bMadeUnhidden = UrlUtil.UnhideFile(m_iocTemp.Path); else // m_bTransacted { try { IOConnection.DeleteFile(m_iocTemp); } catch(Exception) { } } return IOConnection.OpenWrite(m_iocTemp); } public void CommitWrite() { if(m_bTransacted) CommitWriteTransaction(); else // !m_bTransacted { if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); // Hide again } } private void CommitWriteTransaction() { bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) FileSecurity bkSecurity = null; bool bEfsEncrypted = false; #endif if(g_bExtraSafe) { if(!IOConnection.FileExists(m_iocTemp)) throw new FileNotFoundException(m_iocTemp.Path + Environment.NewLine + KLRes.FileSaveFailed); } if(IOConnection.FileExists(m_iocBase)) { #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) if(m_iocBase.IsLocalFile()) { try { FileAttributes faBase = File.GetAttributes(m_iocBase.Path); bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); DateTime tCreation = File.GetCreationTimeUtc(m_iocBase.Path); bkSecurity = File.GetAccessControl(m_iocBase.Path); File.SetCreationTimeUtc(m_iocTemp.Path, tCreation); } catch(Exception) { Debug.Assert(false); } } #endif IOConnection.DeleteFile(m_iocBase); } IOConnection.RenameFile(m_iocTemp, m_iocBase); #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) if(m_iocBase.IsLocalFile()) { try { if(bEfsEncrypted) { try { File.Encrypt(m_iocBase.Path); } catch(Exception) { Debug.Assert(false); } } if(bkSecurity != null) File.SetAccessControl(m_iocBase.Path, bkSecurity); } catch(Exception) { Debug.Assert(false); } } #endif if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again } // For plugins public static void Configure(string strPrefix, bool? obTransacted) { if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; } if(obTransacted.HasValue) g_dEnabled[strPrefix] = obTransacted.Value; else g_dEnabled.Remove(strPrefix); } } }