Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/Database.cpp')
-rw-r--r--src/core/Database.cpp198
1 files changed, 106 insertions, 92 deletions
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index fcd48f1e2..a315adf96 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -18,6 +18,7 @@
#include "Database.h"
+#include "core/AsyncTask.h"
#include "core/Clock.h"
#include "core/FileWatcher.h"
#include "core/Group.h"
@@ -48,7 +49,7 @@ Database::Database()
{
setRootGroup(new Group());
rootGroup()->setUuid(QUuid::createUuid());
- rootGroup()->setName(tr("Root", "Root group name"));
+ rootGroup()->setName(tr("Passwords", "Root group name"));
m_modifiedTimer.setSingleShot(true);
s_uuidMap.insert(m_uuid, this);
@@ -57,7 +58,7 @@ Database::Database()
connect(&m_modifiedTimer, SIGNAL(timeout()), SIGNAL(databaseModified()));
connect(this, SIGNAL(databaseOpened()), SLOT(updateCommonUsernames()));
connect(this, SIGNAL(databaseSaved()), SLOT(updateCommonUsernames()));
- connect(m_fileWatcher, SIGNAL(fileChanged()), SIGNAL(databaseFileChanged()));
+ connect(m_fileWatcher, &FileWatcher::fileChanged, this, &Database::databaseFileChanged);
m_modified = false;
m_emitModified = true;
@@ -111,13 +112,6 @@ bool Database::open(QSharedPointer<const CompositeKey> key, QString* error, bool
*/
bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error, bool readOnly)
{
- if (isInitialized() && m_modified) {
- emit databaseDiscarded();
- }
-
- m_initialized = false;
- setEmitModified(false);
-
QFile dbFile(filePath);
if (!dbFile.exists()) {
if (error) {
@@ -126,17 +120,22 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
return false;
}
- if (!readOnly && !dbFile.open(QIODevice::ReadWrite)) {
- readOnly = true;
- }
-
- if (!dbFile.isOpen() && !dbFile.open(QIODevice::ReadOnly)) {
+ // Don't autodetect read-only mode, as it triggers an upstream bug.
+ // See https://github.com/keepassxreboot/keepassxc/issues/803
+ // if (!readOnly && !dbFile.open(QIODevice::ReadWrite)) {
+ // readOnly = true;
+ // }
+ //
+ // if (!dbFile.isOpen() && !dbFile.open(QIODevice::ReadOnly)) {
+ if (!dbFile.open(QIODevice::ReadOnly)) {
if (error) {
*error = tr("Unable to open file %1.").arg(filePath);
}
return false;
}
+ setEmitModified(false);
+
KeePass2Reader reader;
if (!reader.readDatabase(&dbFile, std::move(key), this)) {
if (error) {
@@ -151,7 +150,6 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
markAsClean();
- m_initialized = true;
emit databaseOpened();
m_fileWatcher->start(canonicalFilePath(), 30, 1);
setEmitModified(true);
@@ -159,6 +157,15 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
return true;
}
+bool Database::isSaving()
+{
+ bool locked = m_saveMutex.tryLock();
+ if (locked) {
+ m_saveMutex.unlock();
+ }
+ return !locked;
+}
+
/**
* Save the database to the current file path. It is an error to call this function
* if no file path has been defined.
@@ -201,6 +208,25 @@ bool Database::save(QString* error, bool atomic, bool backup)
*/
bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool backup)
{
+ // Disallow overlapping save operations
+ if (isSaving()) {
+ if (error) {
+ *error = tr("Database save is already in progress.");
+ }
+ return false;
+ }
+
+ // Never save an uninitialized database
+ if (!isInitialized()) {
+ if (error) {
+ *error = tr("Could not save, database has not been initialized!");
+ }
+ return false;
+ }
+
+ // Prevent destructive operations while saving
+ QMutexLocker locker(&m_saveMutex);
+
if (filePath == m_data.filePath) {
// Disallow saving to the same file if read-only
if (m_data.isReadOnly) {
@@ -225,12 +251,17 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool
setReadOnly(false);
m_fileWatcher->stop();
- auto& canonicalFilePath = QFileInfo::exists(filePath) ? QFileInfo(filePath).canonicalFilePath() : filePath;
- bool ok = performSave(canonicalFilePath, error, atomic, backup);
+ QFileInfo fileInfo(filePath);
+ auto realFilePath = fileInfo.exists() ? fileInfo.canonicalFilePath() : fileInfo.absoluteFilePath();
+ bool isNewFile = !QFile::exists(realFilePath);
+ bool ok = AsyncTask::runAndWaitForFuture([&] { return performSave(realFilePath, error, atomic, backup); });
if (ok) {
markAsClean();
setFilePath(filePath);
- m_fileWatcher->start(canonicalFilePath, 30, 1);
+ if (isNewFile) {
+ QFile::setPermissions(realFilePath, QFile::ReadUser | QFile::WriteUser);
+ }
+ m_fileWatcher->start(realFilePath, 30, 1);
} else {
// Saving failed, don't rewatch file since it does not represent our database
markAsModified();
@@ -277,6 +308,7 @@ bool Database::performSave(const QString& filePath, QString* error, bool atomic,
}
// Delete the original db and move the temp file in place
+ auto perms = QFile::permissions(filePath);
QFile::remove(filePath);
// Note: call into the QFile rename instead of QTemporaryFile
@@ -285,6 +317,7 @@ bool Database::performSave(const QString& filePath, QString* error, bool atomic,
if (tempFile.QFile::rename(filePath)) {
// successfully saved the database
tempFile.setAutoRemove(false);
+ QFile::setPermissions(filePath, perms);
return true;
} else if (!backup || !restoreDatabase(filePath)) {
// Failed to copy new database in place, and
@@ -317,8 +350,8 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
}
PasswordKey oldTransformedKey;
- if (m_data.hasKey) {
- oldTransformedKey.setHash(m_data.transformedMasterKey->rawKey());
+ if (m_data.key->isEmpty()) {
+ oldTransformedKey.setHash(m_data.transformedDatabaseKey->rawKey());
}
KeePass2Writer writer;
@@ -333,7 +366,7 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
return false;
}
- QByteArray newKey = m_data.transformedMasterKey->rawKey();
+ QByteArray newKey = m_data.transformedDatabaseKey->rawKey();
Q_ASSERT(!newKey.isEmpty());
Q_ASSERT(newKey != oldTransformedKey.rawKey());
if (newKey.isEmpty() || newKey == oldTransformedKey.rawKey()) {
@@ -389,30 +422,29 @@ bool Database::import(const QString& xmlExportPath, QString* error)
void Database::releaseData()
{
- s_uuidMap.remove(m_uuid);
- m_uuid = QUuid();
+ // Prevent data release while saving
+ QMutexLocker locker(&m_saveMutex);
if (m_modified) {
emit databaseDiscarded();
}
+ setEmitModified(false);
+ m_modified = false;
+ m_modifiedTimer.stop();
+
+ s_uuidMap.remove(m_uuid);
+ m_uuid = QUuid();
+
m_data.clear();
+ m_metadata->clear();
- if (m_rootGroup && m_rootGroup->parent() == this) {
- delete m_rootGroup;
- }
- if (m_metadata) {
- delete m_metadata;
- }
- if (m_fileWatcher) {
- delete m_fileWatcher;
- }
+ setRootGroup(new Group());
+
+ m_fileWatcher->stop();
m_deletedObjects.clear();
m_commonUsernames.clear();
-
- m_initialized = false;
- m_modified = false;
}
/**
@@ -428,9 +460,12 @@ bool Database::backupDatabase(const QString& filePath)
auto match = re.match(filePath);
auto backupFilePath = filePath;
+ auto perms = QFile::permissions(filePath);
backupFilePath = backupFilePath.replace(re, "") + ".old" + match.captured(1);
QFile::remove(backupFilePath);
- return QFile::copy(filePath, backupFilePath);
+ bool res = QFile::copy(filePath, backupFilePath);
+ QFile::setPermissions(backupFilePath, perms);
+ return res;
}
/**
@@ -446,11 +481,13 @@ bool Database::restoreDatabase(const QString& filePath)
static auto re = QRegularExpression("^(.*?)(\\.[^.]+)?$");
auto match = re.match(filePath);
+ auto perms = QFile::permissions(filePath);
auto backupFilePath = match.captured(1) + ".old" + match.captured(2);
// Only try to restore if the backup file actually exists
if (QFile::exists(backupFilePath)) {
QFile::remove(filePath);
return QFile::copy(backupFilePath, filePath);
+ QFile::setPermissions(filePath, perms);
}
return false;
}
@@ -466,22 +503,14 @@ void Database::setReadOnly(bool readOnly)
}
/**
- * Returns true if database has been fully decrypted and populated, i.e. if
- * it's not just an empty default instance.
+ * Returns true if the database key exists, has subkeys, and the
+ * root group exists
*
* @return true if database has been fully initialized
*/
bool Database::isInitialized() const
{
- return m_initialized;
-}
-
-/**
- * @param initialized true to mark database as initialized
- */
-void Database::setInitialized(bool initialized)
-{
- m_initialized = initialized;
+ return m_data.key && !m_data.key->isEmpty() && m_rootGroup;
}
Group* Database::rootGroup()
@@ -505,7 +534,7 @@ void Database::setRootGroup(Group* group)
{
Q_ASSERT(group);
- if (isInitialized() && m_modified) {
+ if (isInitialized() && isModified()) {
emit databaseDiscarded();
}
@@ -633,9 +662,9 @@ Database::CompressionAlgorithm Database::compressionAlgorithm() const
return m_data.compressionAlgorithm;
}
-QByteArray Database::transformedMasterKey() const
+QByteArray Database::transformedDatabaseKey() const
{
- return m_data.transformedMasterKey->rawKey();
+ return m_data.transformedDatabaseKey->rawKey();
}
QByteArray Database::challengeResponseKey() const
@@ -645,10 +674,11 @@ QByteArray Database::challengeResponseKey() const
bool Database::challengeMasterSeed(const QByteArray& masterSeed)
{
+ m_keyError.clear();
if (m_data.key) {
m_data.masterSeed->setHash(masterSeed);
QByteArray response;
- bool ok = m_data.key->challenge(masterSeed, response);
+ bool ok = m_data.key->challenge(masterSeed, response, &m_keyError);
if (ok && !response.isEmpty()) {
m_data.challengeResponseKey->setHash(response);
} else if (ok && response.isEmpty()) {
@@ -689,11 +719,11 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
bool transformKey)
{
Q_ASSERT(!m_data.isReadOnly);
+ m_keyError.clear();
if (!key) {
m_data.key.reset();
- m_data.transformedMasterKey.reset(new PasswordKey());
- m_data.hasKey = false;
+ m_data.transformedDatabaseKey.reset(new PasswordKey());
return true;
}
@@ -702,59 +732,37 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
Q_ASSERT(!m_data.kdf->seed().isEmpty());
}
- PasswordKey oldTransformedMasterKey;
- if (m_data.hasKey) {
- oldTransformedMasterKey.setHash(m_data.transformedMasterKey->rawKey());
+ PasswordKey oldTransformedDatabaseKey;
+ if (m_data.key && !m_data.key->isEmpty()) {
+ oldTransformedDatabaseKey.setHash(m_data.transformedDatabaseKey->rawKey());
}
- QByteArray transformedMasterKey;
+ QByteArray transformedDatabaseKey;
if (!transformKey) {
- transformedMasterKey = QByteArray(oldTransformedMasterKey.rawKey());
- } else if (!key->transform(*m_data.kdf, transformedMasterKey)) {
+ transformedDatabaseKey = QByteArray(oldTransformedDatabaseKey.rawKey());
+ } else if (!key->transform(*m_data.kdf, transformedDatabaseKey, &m_keyError)) {
return false;
}
m_data.key = key;
- if (!transformedMasterKey.isEmpty()) {
- m_data.transformedMasterKey->setHash(transformedMasterKey);
+ if (!transformedDatabaseKey.isEmpty()) {
+ m_data.transformedDatabaseKey->setHash(transformedDatabaseKey);
}
- m_data.hasKey = true;
if (updateChangedTime) {
- m_metadata->setMasterKeyChanged(Clock::currentDateTimeUtc());
+ m_metadata->setDatabaseKeyChanged(Clock::currentDateTimeUtc());
}
- if (oldTransformedMasterKey.rawKey() != m_data.transformedMasterKey->rawKey()) {
+ if (oldTransformedDatabaseKey.rawKey() != m_data.transformedDatabaseKey->rawKey()) {
markAsModified();
}
return true;
}
-bool Database::hasKey() const
+QString Database::keyError()
{
- return m_data.hasKey;
-}
-
-bool Database::verifyKey(const QSharedPointer<CompositeKey>& key) const
-{
- Q_ASSERT(hasKey());
-
- if (!m_data.challengeResponseKey->rawKey().isEmpty()) {
- QByteArray result;
-
- if (!key->challenge(m_data.masterSeed->rawKey(), result)) {
- // challenge failed, (YubiKey?) removed?
- return false;
- }
-
- if (m_data.challengeResponseKey->rawKey() != result) {
- // wrong response from challenged device(s)
- return false;
- }
- }
-
- return (m_data.key->rawKey() == key->rawKey());
+ return m_keyError;
}
QVariantMap& Database::publicCustomData()
@@ -840,9 +848,9 @@ void Database::setEmitModified(bool value)
m_emitModified = value;
}
-bool Database::isModified() const
+bool Database::isModified(bool includeNonDataChanges) const
{
- return m_modified;
+ return m_modified || (includeNonDataChanges && m_hasNonDataChange);
}
void Database::markAsModified()
@@ -859,11 +867,17 @@ void Database::markAsClean()
bool emitSignal = m_modified;
m_modified = false;
m_modifiedTimer.stop();
+ m_hasNonDataChange = false;
if (emitSignal) {
emit databaseSaved();
}
}
+void Database::markNonDataChange()
+{
+ m_hasNonDataChange = true;
+}
+
/**
* @param uuid UUID of the database
* @return pointer to the database or nullptr if no such database exists
@@ -894,16 +908,16 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
Q_ASSERT(!m_data.isReadOnly);
kdf->randomizeSeed();
- QByteArray transformedMasterKey;
+ QByteArray transformedDatabaseKey;
if (!m_data.key) {
m_data.key = QSharedPointer<CompositeKey>::create();
}
- if (!m_data.key->transform(*kdf, transformedMasterKey)) {
+ if (!m_data.key->transform(*kdf, transformedDatabaseKey)) {
return false;
}
setKdf(kdf);
- m_data.transformedMasterKey->setHash(transformedMasterKey);
+ m_data.transformedDatabaseKey->setHash(transformedDatabaseKey);
markAsModified();
return true;