diff options
author | Christian Kieschnick <christian.kieschnick@hicknhack-software.com> | 2018-10-01 17:26:24 +0300 |
---|---|---|
committer | Jonathan White <support@dmapps.us> | 2018-10-01 17:39:37 +0300 |
commit | eca9c658f4d0a8e956d49ce2e9eea81704e1de9b (patch) | |
tree | f49da9147abee9a96a0acce17548233a988b1f34 /src/core | |
parent | c1e9f45df9f21b7697241037643770a2862bb7ef (diff) |
Add sharing of groups between databases
* Add source folder keeshare for sharing with corresponding define WITH_XC_KEESHARE
* Move common crypto parts to src/crypto/ssh
* Extended OpenSSHKey
* Move filewatching to own file (currently in two related classes DelayedFileWatcher and BulkFileWatcher)
* Small improvements for style and code in several classes
* Sharing is secured using RSA-Keys which are generated on demand
* Publisher signs the container using their private key
* Client can verify the signed container and choose to decline an import,
import only once or trust the publisher and automatically import all
data of this source henceforth
* Integration of settings into Group-Settings, Database-Settings and Application-Settings
* Introduced dependency QuaZip as dependency to allow combined export of
key container and the (custom format) certificate
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/Clock.cpp | 4 | ||||
-rw-r--r-- | src/core/Config.cpp | 9 | ||||
-rw-r--r-- | src/core/Config.h | 3 | ||||
-rw-r--r-- | src/core/Database.cpp | 11 | ||||
-rw-r--r-- | src/core/Database.h | 5 | ||||
-rw-r--r-- | src/core/DatabaseIcons.cpp | 2 | ||||
-rw-r--r-- | src/core/DatabaseIcons.h | 2 | ||||
-rw-r--r-- | src/core/Entry.cpp | 14 | ||||
-rw-r--r-- | src/core/Entry.h | 1 | ||||
-rw-r--r-- | src/core/FileWatcher.cpp | 238 | ||||
-rw-r--r-- | src/core/FileWatcher.h | 92 | ||||
-rw-r--r-- | src/core/TimeInfo.cpp | 6 | ||||
-rw-r--r-- | src/core/Tools.cpp | 27 | ||||
-rw-r--r-- | src/core/Tools.h | 27 |
14 files changed, 429 insertions, 12 deletions
diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp index 02c2ae1bc..88ac4fb77 100644 --- a/src/core/Clock.cpp +++ b/src/core/Clock.cpp @@ -16,7 +16,7 @@ */ #include "Clock.h" -QSharedPointer<Clock> Clock::m_instance = QSharedPointer<Clock>(); +QSharedPointer<Clock> Clock::m_instance; QDateTime Clock::currentDateTimeUtc() { @@ -92,7 +92,7 @@ QDateTime Clock::currentDateTimeImpl() const void Clock::resetInstance() { - m_instance.clear(); + m_instance.reset(); } void Clock::setInstance(Clock* clock) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 8f1dc55fd..8fd2faad9 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -48,7 +48,16 @@ QString Config::getFileName() void Config::set(const QString& key, const QVariant& value) { + if (m_settings->contains(key) && m_settings->value(key) == value) { + return; + } + const bool surpressSignal = !m_settings->contains(key) && m_defaults.value(key) == value; + m_settings->setValue(key, value); + + if (!surpressSignal) { + emit changed(key); + } } /** diff --git a/src/core/Config.h b/src/core/Config.h index fcb27e2ca..347350754 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -43,6 +43,9 @@ public: static void createConfigFromFile(const QString& file); static void createTempFileInstance(); +signals: + void changed(const QString& key); + private: Config(const QString& fileName, QObject* parent); explicit Config(QObject* parent); diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b7a3c07d..607ecc93f 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -111,7 +111,7 @@ void Database::setFilePath(const QString& filePath) m_filePath = filePath; } -Entry* Database::resolveEntry(const QUuid& uuid) +Entry* Database::resolveEntry(const QUuid& uuid) const { return findEntryRecursive(uuid, m_rootGroup); } @@ -121,7 +121,7 @@ Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceT return findEntryRecursive(text, referenceType, m_rootGroup); } -Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group) +Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group) const { const QList<Entry*> entryList = group->entries(); for (Entry* entry : entryList) { @@ -289,8 +289,11 @@ QByteArray Database::challengeResponseKey() const bool Database::challengeMasterSeed(const QByteArray& masterSeed) { - m_data.masterSeed = masterSeed; - return m_data.key->challenge(masterSeed, m_data.challengeResponseKey); + if (m_data.key) { + m_data.masterSeed = masterSeed; + return m_data.key->challenge(masterSeed, m_data.challengeResponseKey); + } + return true; } void Database::setCipher(const QUuid& cipher) diff --git a/src/core/Database.h b/src/core/Database.h index a5ae3effa..9253cb9ea 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -23,6 +23,7 @@ #include <QHash> #include <QObject> +#include "config-keepassx.h" #include "crypto/kdf/Kdf.h" #include "keys/CompositeKey.h" @@ -88,7 +89,7 @@ public: const Metadata* metadata() const; QString filePath() const; void setFilePath(const QString& filePath); - Entry* resolveEntry(const QUuid& uuid); + Entry* resolveEntry(const QUuid& uuid) const; Entry* resolveEntry(const QString& text, EntryReferenceType referenceType); Group* resolveGroup(const QUuid& uuid); QList<DeletedObject> deletedObjects(); @@ -149,7 +150,7 @@ private slots: void startModifiedTimer(); private: - Entry* findEntryRecursive(const QUuid& uuid, Group* group); + Entry* findEntryRecursive(const QUuid& uuid, Group* group) const; Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group); Group* findGroupRecursive(const QUuid& uuid, Group* group); diff --git a/src/core/DatabaseIcons.cpp b/src/core/DatabaseIcons.cpp index ddb4e9106..6219d41f5 100644 --- a/src/core/DatabaseIcons.cpp +++ b/src/core/DatabaseIcons.cpp @@ -22,6 +22,8 @@ DatabaseIcons* DatabaseIcons::m_instance(nullptr); const int DatabaseIcons::IconCount(69); const int DatabaseIcons::ExpiredIconIndex(45); +const int DatabaseIcons::SharedIconIndex(1); +const int DatabaseIcons::UnsharedIconIndex(45); // clang-format off const char* const DatabaseIcons::m_indexToName[] = { diff --git a/src/core/DatabaseIcons.h b/src/core/DatabaseIcons.h index 43a6df216..ecd38fd8a 100644 --- a/src/core/DatabaseIcons.h +++ b/src/core/DatabaseIcons.h @@ -33,6 +33,8 @@ public: static const int IconCount; static const int ExpiredIconIndex; + static const int SharedIconIndex; + static const int UnsharedIconIndex; private: DatabaseIcons(); diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 929447f9c..f3391189c 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -955,6 +955,20 @@ QString Entry::maskPasswordPlaceholders(const QString& str) const return result; } +Entry* Entry::resolveReference(const QString& str) const +{ + QRegularExpressionMatch match = EntryAttributes::matchReference(str); + if (!match.hasMatch()) { + return nullptr; + } + + const QString searchIn = match.captured(EntryAttributes::SearchInGroupName); + const QString searchText = match.captured(EntryAttributes::SearchTextGroupName); + + const EntryReferenceType searchInType = Entry::referenceType(searchIn); + return m_group->database()->resolveEntry(searchText, searchInType); +} + QString Entry::resolveMultiplePlaceholders(const QString& str) const { return resolveMultiplePlaceholdersRecursive(str, ResolveMaximumDepth); diff --git a/src/core/Entry.h b/src/core/Entry.h index 05ed30bc0..94649444b 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -195,6 +195,7 @@ public: Entry* clone(CloneFlags flags) const; void copyDataFrom(const Entry* other); QString maskPasswordPlaceholders(const QString& str) const; + Entry* resolveReference(const QString& str) const; QString resolveMultiplePlaceholders(const QString& str) const; QString resolvePlaceholder(const QString& str) const; QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const; diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp new file mode 100644 index 000000000..ac44174bd --- /dev/null +++ b/src/core/FileWatcher.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 Felix Geyer <debfx@fobos.de> + * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> + * + * 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 or (at your option) + * version 3 of the License. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "FileWatcher.h" + +#include "core/Clock.h" +#include <QFileInfo> + +#ifdef Q_OS_LINUX +#include <sys/vfs.h> +#endif + +namespace +{ + const int FileChangeDelay = 500; + const int TimerResolution = 100; +} + +DelayingFileWatcher::DelayingFileWatcher(QObject* parent) + : QObject(parent) + , m_ignoreFileChange(false) +{ + connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); + connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); + connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged())); + + m_fileChangeDelayTimer.setSingleShot(true); + m_fileUnblockTimer.setSingleShot(true); +} + +void DelayingFileWatcher::restart() +{ + m_fileWatcher.addPath(m_filePath); +} + +void DelayingFileWatcher::stop() +{ + m_fileWatcher.removePath(m_filePath); +} + +void DelayingFileWatcher::start(const QString& filePath) +{ + if (!m_filePath.isEmpty()) { + m_fileWatcher.removePath(m_filePath); + } + +#if defined(Q_OS_LINUX) + struct statfs statfsBuf; + bool forcePolling = false; + const auto NFS_SUPER_MAGIC = 0x6969; + + if (!statfs(filePath.toLocal8Bit().constData(), &statfsBuf)) { + forcePolling = (statfsBuf.f_type == NFS_SUPER_MAGIC); + } else { + // if we can't get the fs type let's fall back to polling + forcePolling = true; + } + auto objectName = forcePolling ? QLatin1String("_qt_autotest_force_engine_poller") : QLatin1String(""); + m_fileWatcher.setObjectName(objectName); +#endif + + m_fileWatcher.addPath(filePath); + + if (!filePath.isEmpty()) { + m_filePath = filePath; + } +} + +void DelayingFileWatcher::ignoreFileChanges() +{ + m_ignoreFileChange = true; + m_fileChangeDelayTimer.stop(); +} + +void DelayingFileWatcher::observeFileChanges(bool delayed) +{ + int timeout = 0; + if (delayed) { + timeout = FileChangeDelay; + } else { + m_ignoreFileChange = false; + start(m_filePath); + } + if (timeout > 0 && !m_fileUnblockTimer.isActive()) { + m_fileUnblockTimer.start(timeout); + } +} + +void DelayingFileWatcher::onWatchedFileChanged() +{ + if (m_ignoreFileChange) { + // the client forcefully silenced us + return; + } + if (m_fileChangeDelayTimer.isActive()) { + // we are waiting to fire the delayed fileChanged event, so nothing + // to do here + return; + } + + m_fileChangeDelayTimer.start(FileChangeDelay); +} + +BulkFileWatcher::BulkFileWatcher(QObject* parent) + : QObject(parent) +{ + connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileChanged(QString))); + connect(&m_fileWatcher, SIGNAL(directoryChanged(QString)), SLOT(handleDirectoryChanged(QString))); + connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); + m_fileWatchUnblockTimer.setSingleShot(true); +} + +void BulkFileWatcher::clear() +{ + for (const QString& path : m_fileWatcher.files() + m_fileWatcher.directories()) { + const QFileInfo info(path); + m_fileWatcher.removePath(info.absoluteFilePath()); + m_fileWatcher.removePath(info.absolutePath()); + } + m_filePaths.clear(); + m_watchedFilesInDirectory.clear(); + m_ignoreFilesChangess.clear(); +} + +void BulkFileWatcher::removePath(const QString& path) +{ + const QFileInfo info(path); + m_fileWatcher.removePath(info.absoluteFilePath()); + m_fileWatcher.removePath(info.absolutePath()); + m_filePaths.remove(info.absoluteFilePath()); + m_filePaths.remove(info.absolutePath()); + m_watchedFilesInDirectory[info.absolutePath()].remove(info.absoluteFilePath()); +} + +void BulkFileWatcher::addPath(const QString& path) +{ + const QFileInfo info(path); + m_fileWatcher.addPath(info.absoluteFilePath()); + m_fileWatcher.addPath(info.absolutePath()); + m_filePaths.insert(info.absoluteFilePath()); + m_filePaths.insert(info.absolutePath()); + m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists(); +} + +void BulkFileWatcher::restart(const QString& path) +{ + const QFileInfo info(path); + Q_ASSERT(m_filePaths.contains(info.absoluteFilePath())); + Q_ASSERT(m_filePaths.contains(info.absolutePath())); + m_fileWatcher.addPath(info.absoluteFilePath()); + m_fileWatcher.addPath(info.absolutePath()); +} + +void BulkFileWatcher::handleFileChanged(const QString& path) +{ + addPath(path); + + const QFileInfo info(path); + if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) { + // changes are blocked + return; + } + + emit fileChanged(path); +} + +void BulkFileWatcher::handleDirectoryChanged(const QString& path) +{ + qDebug("Directory changed %s", qPrintable(path)); + const QFileInfo directory(path); + const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directory.absolutePath()]; + for (const QString& file : watchedFiles.keys()) { + const QFileInfo info(file); + const bool existed = watchedFiles[info.absoluteFilePath()]; + if (!info.exists() && existed) { + qDebug("Remove watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.removePath(info.absolutePath()); + emit fileRemoved(info.absoluteFilePath()); + } + if (!existed && info.exists()) { + qDebug("Add watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.addPath(info.absolutePath()); + emit fileCreated(info.absoluteFilePath()); + } + if (existed && info.exists()) { + qDebug("Refresh watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.removePath(info.absolutePath()); + m_fileWatcher.addPath(info.absolutePath()); + emit fileChanged(info.absoluteFilePath()); + } + m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists(); + } +} + +void BulkFileWatcher::ignoreFileChanges(const QString& path) +{ + const QFileInfo info(path); + m_ignoreFilesChangess[info.canonicalFilePath()] = Clock::currentDateTimeUtc().addMSecs(FileChangeDelay); +} + +void BulkFileWatcher::observeFileChanges(bool delayed) +{ + int timeout = 0; + if (delayed) { + timeout = TimerResolution; + } else { + const QDateTime current = Clock::currentDateTimeUtc(); + for (const QString& key : m_ignoreFilesChangess.keys()) { + if (m_ignoreFilesChangess[key] < current) { + // We assume that there was no concurrent change of the database + // during our block - so no need to reimport + qDebug("Remove block from %s", qPrintable(key)); + m_ignoreFilesChangess.remove(key); + continue; + } + qDebug("Keep block from %s", qPrintable(key)); + timeout = static_cast<int>(current.msecsTo(m_ignoreFilesChangess[key])); + } + } + if (timeout > 0 && !m_fileWatchUnblockTimer.isActive()) { + m_fileWatchUnblockTimer.start(timeout); + } +} diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h new file mode 100644 index 000000000..de7dbb1c2 --- /dev/null +++ b/src/core/FileWatcher.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org> + * + * 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 or (at your option) + * version 3 of the License. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef KEEPASSXC_FILEWATCHER_H +#define KEEPASSXC_FILEWATCHER_H + +#include <QFileSystemWatcher> +#include <QSet> +#include <QTimer> +#include <QVariant> + +class DelayingFileWatcher : public QObject +{ + Q_OBJECT + +public: + explicit DelayingFileWatcher(QObject* parent = nullptr); + + void blockAutoReload(bool block); + void start(const QString& path); + + void restart(); + void stop(); + void ignoreFileChanges(); + +signals: + void fileChanged(); + +public slots: + void observeFileChanges(bool delayed = false); + +private slots: + void onWatchedFileChanged(); + +private: + QString m_filePath; + QFileSystemWatcher m_fileWatcher; + QTimer m_fileChangeDelayTimer; + QTimer m_fileUnblockTimer; + bool m_ignoreFileChange; +}; + +class BulkFileWatcher : public QObject +{ + Q_OBJECT +public: + explicit BulkFileWatcher(QObject* parent = nullptr); + + void clear(); + + void removePath(const QString& path); + void addPath(const QString& path); + + void restart(const QString& path); + + void ignoreFileChanges(const QString& path); + +signals: + void fileCreated(QString); + void fileChanged(QString); + void fileRemoved(QString); + +public slots: + void observeFileChanges(bool delayed = false); + +private slots: + void handleFileChanged(const QString& path); + void handleDirectoryChanged(const QString& path); + +private: + QSet<QString> m_filePaths; + QMap<QString, QDateTime> m_ignoreFilesChangess; + QFileSystemWatcher m_fileWatcher; + QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory; + QTimer m_fileWatchUnblockTimer; // needed for Import/Export-References +}; + +#endif // KEEPASSXC_FILEWATCHER_H diff --git a/src/core/TimeInfo.cpp b/src/core/TimeInfo.cpp index c774a7c81..b48ad42ea 100644 --- a/src/core/TimeInfo.cpp +++ b/src/core/TimeInfo.cpp @@ -124,8 +124,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const if (::compare(m_creationTime, other.m_creationTime, options) != 0) { return false; } - if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options) - != 0) { + if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options) != 0) { return false; } if (::compare(m_expires, m_expiryTime, other.m_expires, other.expiryTime(), options) != 0) { @@ -134,8 +133,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_usageCount, other.m_usageCount, options) != 0) { return false; } - if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options) - != 0) { + if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options) != 0) { return false; } return true; diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 458d42988..2dc75873b 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -346,4 +346,31 @@ namespace Tools return bSuccess; } + + Buffer::Buffer() + : raw(nullptr) + , size(0) + { + + } + + Buffer::~Buffer() + { + clear(); + } + + void Buffer::clear() + { + if(size > 0){ + free(raw); + } + raw = nullptr; size = 0; + } + + QByteArray Buffer::content() const + { + return QByteArray(reinterpret_cast<char*>(raw), size ); + } + + } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index 4f75b750b..c1814f756 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -56,6 +56,33 @@ namespace Tools } } + template <typename Key, typename Value, void deleter(Value)> struct Map + { + QMap<Key, Value> values; + Value& operator[](const Key index) + { + return values[index]; + } + + ~Map() + { + for (Value m : values) { + deleter(m); + } + } + }; + + struct Buffer + { + unsigned char* raw; + size_t size; + + Buffer(); + ~Buffer(); + + void clear(); + QByteArray content() const; + }; } // namespace Tools #endif // KEEPASSX_TOOLS_H |