2019-07-25 16:39:43 +02:00
|
|
|
|
/*
|
|
|
|
|
KeePass Password Safe - The Open-Source Password Manager
|
2020-03-16 15:28:05 +01:00
|
|
|
|
Copyright (C) 2003-2020 Dominik Reichl <dominik.reichl@t-online.de>
|
2019-07-25 16:39:43 +02:00
|
|
|
|
|
|
|
|
|
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.ComponentModel;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
2020-03-23 18:35:00 +01:00
|
|
|
|
|
|
|
|
|
#if !ModernKeePassLib && !KeePassLibSD
|
2019-07-25 16:39:43 +02:00
|
|
|
|
using System.Security.AccessControl;
|
|
|
|
|
#endif
|
2019-07-26 18:28:53 +02:00
|
|
|
|
|
2019-07-25 16:39:43 +02:00
|
|
|
|
using ModernKeePassLib.Cryptography;
|
2019-07-26 18:28:53 +02:00
|
|
|
|
using ModernKeePassLib.Delegates;
|
|
|
|
|
using ModernKeePassLib.Native;
|
2019-07-25 16:39:43 +02:00
|
|
|
|
using ModernKeePassLib.Resources;
|
2019-07-26 18:28:53 +02:00
|
|
|
|
using ModernKeePassLib.Utility;
|
2019-07-25 16:39:43 +02:00
|
|
|
|
|
|
|
|
|
namespace ModernKeePassLib.Serialization
|
|
|
|
|
{
|
|
|
|
|
public sealed class FileTransactionEx : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private bool m_bTransacted;
|
|
|
|
|
private IOConnectionInfo m_iocBase; // Null means disposed
|
|
|
|
|
private IOConnectionInfo m_iocTemp;
|
|
|
|
|
private IOConnectionInfo m_iocTxfMidFallback = null; // Null <=> TxF not used
|
|
|
|
|
|
|
|
|
|
private bool m_bMadeUnhidden = false;
|
|
|
|
|
private List<IOConnectionInfo> m_lToDelete = new List<IOConnectionInfo>();
|
|
|
|
|
|
2020-03-16 15:28:05 +01:00
|
|
|
|
internal const string StrTempSuffix = ".tmp";
|
2019-07-25 16:39:43 +02:00
|
|
|
|
private static readonly string StrTxfTempPrefix = PwDefs.ShortProductName + "_TxF_";
|
2020-03-16 15:28:05 +01:00
|
|
|
|
internal const string StrTxfTempSuffix = ".tmp";
|
2019-07-25 16:39:43 +02:00
|
|
|
|
|
|
|
|
|
private static Dictionary<string, bool> g_dEnabled =
|
|
|
|
|
new Dictionary<string, bool>(StrUtil.CaseIgnoreComparer);
|
|
|
|
|
|
|
|
|
|
private static bool g_bExtraSafe = false;
|
|
|
|
|
internal static bool ExtraSafe
|
|
|
|
|
{
|
|
|
|
|
get { return g_bExtraSafe; }
|
|
|
|
|
set { g_bExtraSafe = value; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FileTransactionEx(IOConnectionInfo iocBaseFile) :
|
|
|
|
|
this(iocBaseFile, true)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted)
|
|
|
|
|
{
|
|
|
|
|
if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile");
|
|
|
|
|
|
|
|
|
|
m_bTransacted = bTransacted;
|
|
|
|
|
|
|
|
|
|
m_iocBase = iocBaseFile.CloneDeep();
|
|
|
|
|
if(m_iocBase.IsLocalFile())
|
|
|
|
|
m_iocBase.Path = UrlUtil.GetShortestAbsolutePath(m_iocBase.Path);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If the base and the temporary file are in different
|
|
|
|
|
// folders and the base file doesn't exist (i.e. we can't
|
|
|
|
|
// backup the ACL), a transaction would cause the new file
|
|
|
|
|
// to have the default ACL of the temporary folder instead
|
|
|
|
|
// of the one of the base folder; therefore, we don't use
|
|
|
|
|
// a transaction when the base file doesn't exist (this
|
|
|
|
|
// also results in other applications monitoring the folder
|
|
|
|
|
// to see one file creation only)
|
|
|
|
|
m_bTransacted = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if !ModernKeePassLib
|
|
|
|
|
// 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<string, bool> 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;
|
|
|
|
|
|
|
|
|
|
TxfPrepare(); // Adjusts m_iocTemp
|
|
|
|
|
}
|
|
|
|
|
else m_iocTemp = m_iocBase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~FileTransactionEx()
|
|
|
|
|
{
|
|
|
|
|
Dispose(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Dispose(bool bDisposing)
|
|
|
|
|
{
|
|
|
|
|
m_iocBase = null;
|
|
|
|
|
if(!bDisposing) return;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach(IOConnectionInfo ioc in m_lToDelete)
|
|
|
|
|
{
|
|
|
|
|
if(IOConnection.FileExists(ioc, false))
|
|
|
|
|
IOConnection.DeleteFile(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lToDelete.Clear();
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Stream OpenWrite()
|
|
|
|
|
{
|
|
|
|
|
if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
|
|
|
|
|
|
|
|
|
|
if(!m_bTransacted) m_bMadeUnhidden |= UrlUtil.UnhideFile(m_iocTemp.Path);
|
|
|
|
|
|
|
|
|
|
return IOConnection.OpenWrite(m_iocTemp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CommitWrite()
|
|
|
|
|
{
|
|
|
|
|
if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
|
|
|
|
|
|
|
|
|
|
if(!m_bTransacted)
|
|
|
|
|
{
|
|
|
|
|
if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true);
|
|
|
|
|
}
|
|
|
|
|
else CommitWriteTransaction();
|
|
|
|
|
|
|
|
|
|
m_iocBase = null; // Dispose
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CommitWriteTransaction()
|
|
|
|
|
{
|
|
|
|
|
if(g_bExtraSafe)
|
|
|
|
|
{
|
|
|
|
|
if(!IOConnection.FileExists(m_iocTemp))
|
|
|
|
|
throw new FileNotFoundException(m_iocTemp.Path +
|
|
|
|
|
MessageService.NewLine + KLRes.FileSaveFailed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path);
|
|
|
|
|
|
|
|
|
|
#if !ModernKeePassLib
|
|
|
|
|
// 'All' includes 'Audit' (SACL), which requires SeSecurityPrivilege,
|
|
|
|
|
// which we usually don't have and therefore get an exception;
|
|
|
|
|
// trying to set 'Owner' or 'Group' can result in an
|
|
|
|
|
// UnauthorizedAccessException; thus we restore 'Access' (DACL) only
|
|
|
|
|
const AccessControlSections acs = AccessControlSections.Access;
|
|
|
|
|
#endif
|
|
|
|
|
bool bEfsEncrypted = false;
|
|
|
|
|
byte[] pbSec = null;
|
2019-07-26 18:28:53 +02:00
|
|
|
|
|
2019-07-25 16:39:43 +02:00
|
|
|
|
DateTime? otCreation = null;
|
2020-03-16 15:28:05 +01:00
|
|
|
|
SimpleStat sStat = null;
|
2019-07-25 16:39:43 +02:00
|
|
|
|
|
|
|
|
|
bool bBaseExists = IOConnection.FileExists(m_iocBase);
|
|
|
|
|
if(bBaseExists && m_iocBase.IsLocalFile())
|
|
|
|
|
{
|
|
|
|
|
// FileAttributes faBase = FileAttributes.Normal;
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-03-23 18:35:00 +01:00
|
|
|
|
#if !(ModernKeePassLib || KeePassUAP)
|
2019-07-25 16:39:43 +02:00
|
|
|
|
FileAttributes faBase = File.GetAttributes(m_iocBase.Path);
|
|
|
|
|
bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0);
|
|
|
|
|
try { if(bEfsEncrypted) File.Decrypt(m_iocBase.Path); } // For TxF
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
2020-03-23 18:35:00 +01:00
|
|
|
|
|
2019-07-25 16:39:43 +02:00
|
|
|
|
otCreation = File.GetCreationTimeUtc(m_iocBase.Path);
|
2020-03-16 15:28:05 +01:00
|
|
|
|
sStat = SimpleStat.Get(m_iocBase.Path);
|
2020-03-23 18:35:00 +01:00
|
|
|
|
|
2019-07-25 16:39:43 +02:00
|
|
|
|
// May throw with Mono
|
|
|
|
|
FileSecurity sec = File.GetAccessControl(m_iocBase.Path, acs);
|
|
|
|
|
if(sec != null) pbSec = sec.GetSecurityDescriptorBinaryForm();
|
|
|
|
|
#endif
|
2019-07-31 11:48:42 +02:00
|
|
|
|
}
|
2019-07-25 16:39:43 +02:00
|
|
|
|
catch(Exception) { Debug.Assert(NativeLib.IsUnix()); }
|
|
|
|
|
|
|
|
|
|
// if((long)(faBase & FileAttributes.ReadOnly) != 0)
|
|
|
|
|
// throw new UnauthorizedAccessException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!TxfMove())
|
|
|
|
|
{
|
|
|
|
|
if(bBaseExists) IOConnection.DeleteFile(m_iocBase);
|
|
|
|
|
IOConnection.RenameFile(m_iocTemp, m_iocBase);
|
|
|
|
|
}
|
|
|
|
|
else { Debug.Assert(pbSec != null); } // TxF success => NTFS => has ACL
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// If File.GetCreationTimeUtc fails, it may return a
|
|
|
|
|
// date with year 1601, and Unix times start in 1970,
|
|
|
|
|
// so testing for 1971 should ensure validity;
|
|
|
|
|
// https://msdn.microsoft.com/en-us/library/system.io.file.getcreationtimeutc.aspx
|
|
|
|
|
#if !ModernKeePassLib
|
|
|
|
|
if(otCreation.HasValue && (otCreation.Value.Year >= 1971))
|
|
|
|
|
File.SetCreationTimeUtc(m_iocBase.Path, otCreation.Value);
|
|
|
|
|
|
2020-03-16 15:28:05 +01:00
|
|
|
|
if(sStat != null) SimpleStat.Set(m_iocBase.Path, sStat);
|
|
|
|
|
|
2019-07-25 16:39:43 +02:00
|
|
|
|
if(bEfsEncrypted)
|
|
|
|
|
{
|
|
|
|
|
try { File.Encrypt(m_iocBase.Path); }
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// File.SetAccessControl(m_iocBase.Path, secPrev);
|
|
|
|
|
// Directly calling File.SetAccessControl with the previous
|
|
|
|
|
// FileSecurity object does not work; the binary form
|
|
|
|
|
// indirection is required;
|
|
|
|
|
// https://sourceforge.net/p/keepass/bugs/1738/
|
|
|
|
|
// https://msdn.microsoft.com/en-us/library/system.io.file.setaccesscontrol.aspx
|
|
|
|
|
if((pbSec != null) && (pbSec.Length != 0))
|
|
|
|
|
{
|
|
|
|
|
FileSecurity sec = new FileSecurity();
|
|
|
|
|
sec.SetSecurityDescriptorBinaryForm(pbSec, acs);
|
|
|
|
|
|
|
|
|
|
File.SetAccessControl(m_iocBase.Path, sec);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
|
|
|
|
|
if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool TxfIsSupported(char chDriveLetter)
|
|
|
|
|
{
|
|
|
|
|
if(chDriveLetter == '\0') return false;
|
|
|
|
|
|
|
|
|
|
#if ModernKeePassLib
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string strRoot = (new string(chDriveLetter, 1)) + ":\\";
|
|
|
|
|
|
|
|
|
|
const int cch = NativeMethods.MAX_PATH + 1;
|
|
|
|
|
StringBuilder sbName = new StringBuilder(cch + 1);
|
|
|
|
|
uint uSerial = 0, cchMaxComp = 0, uFlags = 0;
|
|
|
|
|
StringBuilder sbFileSystem = new StringBuilder(cch + 1);
|
|
|
|
|
|
|
|
|
|
if(!NativeMethods.GetVolumeInformation(strRoot, sbName, (uint)cch,
|
|
|
|
|
ref uSerial, ref cchMaxComp, ref uFlags, sbFileSystem, (uint)cch))
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, (new Win32Exception()).Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ((uFlags & NativeMethods.FILE_SUPPORTS_TRANSACTIONS) != 0);
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void TxfPrepare()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if(NativeLib.IsUnix()) return;
|
|
|
|
|
if(!m_iocBase.IsLocalFile()) return;
|
2020-03-16 15:28:05 +01:00
|
|
|
|
if(TxfIsUnusable()) return;
|
2019-07-25 16:39:43 +02:00
|
|
|
|
|
|
|
|
|
string strID = StrUtil.AlphaNumericOnly(Convert.ToBase64String(
|
|
|
|
|
CryptoRandom.Instance.GetRandomBytes(16)));
|
|
|
|
|
string strTempDir = UrlUtil.GetTempPath();
|
|
|
|
|
// See also ClearOld method
|
|
|
|
|
string strTemp = UrlUtil.EnsureTerminatingSeparator(strTempDir,
|
|
|
|
|
false) + StrTxfTempPrefix + strID + StrTxfTempSuffix;
|
|
|
|
|
|
|
|
|
|
char chB = UrlUtil.GetDriveLetter(m_iocBase.Path);
|
|
|
|
|
char chT = UrlUtil.GetDriveLetter(strTemp);
|
|
|
|
|
if(!TxfIsSupported(chB)) return;
|
|
|
|
|
if((chT != chB) && !TxfIsSupported(chT)) return;
|
|
|
|
|
|
|
|
|
|
m_iocTxfMidFallback = m_iocTemp;
|
|
|
|
|
m_iocTemp = IOConnectionInfo.FromPath(strTemp);
|
|
|
|
|
|
|
|
|
|
m_lToDelete.Add(m_iocTemp);
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); m_iocTxfMidFallback = null; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TxfMove()
|
|
|
|
|
{
|
|
|
|
|
if(m_iocTxfMidFallback == null) return false;
|
|
|
|
|
|
|
|
|
|
if(TxfMoveWithTx()) return true;
|
|
|
|
|
|
2019-07-31 11:48:42 +02:00
|
|
|
|
// Move the temporary file onto the base file's drive first,
|
|
|
|
|
// such that it cannot happen that both the base file and
|
|
|
|
|
// the temporary file are deleted/corrupted
|
2019-07-26 18:28:53 +02:00
|
|
|
|
#if !ModernKeePassLib
|
2019-07-25 16:39:43 +02:00
|
|
|
|
const uint f = (NativeMethods.MOVEFILE_COPY_ALLOWED |
|
|
|
|
|
NativeMethods.MOVEFILE_REPLACE_EXISTING);
|
|
|
|
|
bool b = NativeMethods.MoveFileEx(m_iocTemp.Path, m_iocTxfMidFallback.Path, f);
|
|
|
|
|
if(b) b = NativeMethods.MoveFileEx(m_iocTxfMidFallback.Path, m_iocBase.Path, f);
|
|
|
|
|
if(!b) throw new Win32Exception();
|
|
|
|
|
|
|
|
|
|
Debug.Assert(!File.Exists(m_iocTemp.Path));
|
|
|
|
|
Debug.Assert(!File.Exists(m_iocTxfMidFallback.Path));
|
|
|
|
|
#endif
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TxfMoveWithTx()
|
|
|
|
|
{
|
2019-07-26 18:28:53 +02:00
|
|
|
|
#if !ModernKeePassLib
|
2019-07-25 16:39:43 +02:00
|
|
|
|
IntPtr hTx = new IntPtr((int)NativeMethods.INVALID_HANDLE_VALUE);
|
|
|
|
|
Debug.Assert(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string strTx = PwDefs.ShortProductName + " TxF - " +
|
|
|
|
|
StrUtil.AlphaNumericOnly(Convert.ToBase64String(
|
|
|
|
|
CryptoRandom.Instance.GetRandomBytes(16)));
|
|
|
|
|
const int mchTx = NativeMethods.MAX_TRANSACTION_DESCRIPTION_LENGTH;
|
|
|
|
|
if(strTx.Length >= mchTx) strTx = strTx.Substring(0, mchTx - 1);
|
|
|
|
|
|
|
|
|
|
hTx = NativeMethods.CreateTransaction(IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero, 0, 0, 0, 0, strTx);
|
|
|
|
|
if(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE)
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, (new Win32Exception()).Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!NativeMethods.MoveFileTransacted(m_iocTemp.Path, m_iocBase.Path,
|
|
|
|
|
IntPtr.Zero, IntPtr.Zero, (NativeMethods.MOVEFILE_COPY_ALLOWED |
|
|
|
|
|
NativeMethods.MOVEFILE_REPLACE_EXISTING), hTx))
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, (new Win32Exception()).Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!NativeMethods.CommitTransaction(hTx))
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, (new Win32Exception()).Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug.Assert(!File.Exists(m_iocTemp.Path));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if(hTx.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE)
|
|
|
|
|
{
|
|
|
|
|
try { if(!NativeMethods.CloseHandle(hTx)) { Debug.Assert(false); } }
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-03-16 15:28:05 +01:00
|
|
|
|
return false;
|
2019-07-25 16:39:43 +02:00
|
|
|
|
}
|
2019-07-26 18:28:53 +02:00
|
|
|
|
|
2020-03-16 15:28:05 +01:00
|
|
|
|
private bool TxfIsUnusable()
|
2019-07-26 18:28:53 +02:00
|
|
|
|
{
|
|
|
|
|
#if !ModernKeePassLib
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string strReleaseId = (Registry.GetValue(
|
|
|
|
|
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
|
|
|
|
"ReleaseId", string.Empty) as string);
|
2020-03-16 15:28:05 +01:00
|
|
|
|
|
|
|
|
|
// Due to a bug in Microsoft's 'cldflt.sys' driver, a TxF transaction
|
|
|
|
|
// results in a Blue Screen of Death on Windows 10 1903/1909;
|
|
|
|
|
// https://www.windowslatest.com/2019/10/20/windows-10-update-issues-bsod-broken-apps-and-defender-atp/
|
|
|
|
|
// https://sourceforge.net/p/keepass/discussion/329221/thread/924b94ea48/
|
|
|
|
|
// This bug is fixed by the Windows update 4530684;
|
|
|
|
|
// https://support.microsoft.com/en-us/help/4530684/windows-10-update-kb4530684
|
|
|
|
|
// if(strReleaseId == "1903") return true;
|
|
|
|
|
// if(strReleaseId == "1909") return true;
|
|
|
|
|
|
2019-07-26 18:28:53 +02:00
|
|
|
|
if(strReleaseId != "1809") return false;
|
|
|
|
|
|
2020-03-16 15:28:05 +01:00
|
|
|
|
// On Windows 10 1809, OneDrive crashes if the file is
|
|
|
|
|
// in a OneDrive folder;
|
|
|
|
|
// https://sourceforge.net/p/keepass/discussion/329220/thread/672ffecc65/
|
|
|
|
|
// https://sourceforge.net/p/keepass/discussion/329221/thread/514786c23a/
|
|
|
|
|
|
2019-07-26 18:28:53 +02:00
|
|
|
|
string strFile = m_iocBase.Path;
|
|
|
|
|
|
|
|
|
|
GFunc<string, string, bool> fMatch = delegate(string strRoot, string strSfx)
|
|
|
|
|
{
|
|
|
|
|
if(string.IsNullOrEmpty(strRoot)) return false;
|
|
|
|
|
string strPfx = UrlUtil.EnsureTerminatingSeparator(
|
|
|
|
|
strRoot, false) + strSfx;
|
|
|
|
|
return strFile.StartsWith(strPfx, StrUtil.CaseIgnoreCmp);
|
|
|
|
|
};
|
|
|
|
|
GFunc<string, string, bool> fMatchEnv = delegate(string strEnv, string strSfx)
|
|
|
|
|
{
|
|
|
|
|
return fMatch(Environment.GetEnvironmentVariable(strEnv), strSfx);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
string strKnown = NativeMethods.GetKnownFolderPath(
|
|
|
|
|
NativeMethods.FOLDERID_SkyDrive);
|
|
|
|
|
if(fMatch(strKnown, string.Empty)) return true;
|
|
|
|
|
|
|
|
|
|
if(fMatchEnv("USERPROFILE", "OneDrive\\")) return true;
|
|
|
|
|
if(fMatchEnv("OneDrive", string.Empty)) return true;
|
|
|
|
|
if(fMatchEnv("OneDriveCommercial", string.Empty)) return true;
|
|
|
|
|
if(fMatchEnv("OneDriveConsumer", string.Empty)) return true;
|
|
|
|
|
|
|
|
|
|
using(RegistryKey kAccs = Registry.CurrentUser.OpenSubKey(
|
|
|
|
|
"Software\\Microsoft\\OneDrive\\Accounts", false))
|
|
|
|
|
{
|
|
|
|
|
string[] vAccs = (((kAccs != null) ? kAccs.GetSubKeyNames() :
|
|
|
|
|
null) ?? new string[0]);
|
|
|
|
|
|
|
|
|
|
foreach(string strAcc in vAccs)
|
|
|
|
|
{
|
|
|
|
|
if(string.IsNullOrEmpty(strAcc)) { Debug.Assert(false); continue; }
|
|
|
|
|
|
|
|
|
|
using(RegistryKey kTenants = kAccs.OpenSubKey(
|
|
|
|
|
strAcc + "\\Tenants", false))
|
|
|
|
|
{
|
|
|
|
|
string[] vTenants = (((kTenants != null) ?
|
|
|
|
|
kTenants.GetSubKeyNames() : null) ?? new string[0]);
|
|
|
|
|
|
|
|
|
|
foreach(string strT in vTenants)
|
|
|
|
|
{
|
|
|
|
|
if(string.IsNullOrEmpty(strT)) { Debug.Assert(false); continue; }
|
|
|
|
|
|
|
|
|
|
using(RegistryKey kT = kTenants.OpenSubKey(strT, false))
|
|
|
|
|
{
|
|
|
|
|
string[] vPaths = (((kT != null) ?
|
|
|
|
|
kT.GetValueNames() : null) ?? new string[0]);
|
|
|
|
|
|
|
|
|
|
foreach(string strPath in vPaths)
|
|
|
|
|
{
|
|
|
|
|
if((strPath == null) || (strPath.Length < 4) ||
|
|
|
|
|
(strPath[1] != ':'))
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(fMatch(strPath, string.Empty)) return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-23 18:35:00 +01:00
|
|
|
|
#if !ModernKeePassLib
|
|
|
|
|
internal static void ClearOld()
|
2020-03-16 15:28:05 +01:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// See also TxfPrepare method
|
|
|
|
|
DirectoryInfo di = new DirectoryInfo(UrlUtil.GetTempPath());
|
|
|
|
|
List<FileInfo> l = UrlUtil.GetFileInfos(di, StrTxfTempPrefix +
|
|
|
|
|
"*" + StrTxfTempSuffix, SearchOption.TopDirectoryOnly);
|
|
|
|
|
|
|
|
|
|
foreach(FileInfo fi in l)
|
|
|
|
|
{
|
|
|
|
|
if(fi == null) { Debug.Assert(false); continue; }
|
|
|
|
|
if(!fi.Name.StartsWith(StrTxfTempPrefix, StrUtil.CaseIgnoreCmp) ||
|
|
|
|
|
!fi.Name.EndsWith(StrTxfTempSuffix, StrUtil.CaseIgnoreCmp))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalDays > 1.0)
|
|
|
|
|
fi.Delete();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch(Exception) { Debug.Assert(false); }
|
|
|
|
|
}
|
2020-03-23 18:35:00 +01:00
|
|
|
|
#endif
|
2019-07-25 16:39:43 +02:00
|
|
|
|
}
|
|
|
|
|
}
|