diff --git a/CMakeLists.txt b/CMakeLists.txt index 226460987..2c79261f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,6 +438,14 @@ if(UNIX) int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }" HAVE_PR_SET_DUMPABLE) + check_cxx_source_compiles("#include + int main() { return 0; }" + HAVE_MALLOC_H) + + check_cxx_source_compiles("#include + int main() { malloc_usable_size(NULL, 0); return 0; }" + HAVE_MALLOC_USABLE_SIZE) + check_cxx_source_compiles("#include int main() { struct rlimit limit; diff --git a/share/docs/man/keepassxc-cli.1 b/share/docs/man/keepassxc-cli.1 index 15d0fedc1..bcc97efae 100644 --- a/share/docs/man/keepassxc-cli.1 +++ b/share/docs/man/keepassxc-cli.1 @@ -182,6 +182,10 @@ an error if no TOTP is configured for the entry. Shows the named attributes. This option can be specified more than once, with each attribute shown one-per-line in the given order. If no attributes are specified and \fI-t\fP is not specified, a summary of the default attributes is given. +Protected attributes will be displayed in clear text if specified explicitly by this option. + +.IP "-s, --show-protected" +Shows the protected attributes in clear text. .IP "-t, --totp" Also shows the current TOTP, reporting an error if no TOTP is configured for diff --git a/share/translations/CMakeLists.txt b/share/translations/CMakeLists.txt index 5ca739695..ffd4698b0 100644 --- a/share/translations/CMakeLists.txt +++ b/share/translations/CMakeLists.txt @@ -33,5 +33,9 @@ endif() set(QM_FILES ${QM_FILES} ${QTBASE_TRANSLATIONS}) install(FILES ${QM_FILES} DESTINATION ${DATA_INSTALL_DIR}/translations) + +# Add keepassx_en.qm as a fallback for uncommon english locales +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/keepassx_en_US.qm DESTINATION ${DATA_INSTALL_DIR}/translations RENAME keepassx_en.qm) + add_custom_target(translations DEPENDS ${QM_FILES}) add_dependencies(${PROGNAME} translations) diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 2b1dfdc1b..482ad8a13 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -46,7 +46,7 @@ Clip::Clip() int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { const QStringList args = parser->positionalArguments(); - QString entryPath = args.at(1); + const QString& entryPath = args.at(1); QString timeout; if (args.size() == 3) { timeout = args.at(2); diff --git a/src/cli/Import.cpp b/src/cli/Import.cpp index ff6fb266b..0907f00ab 100644 --- a/src/cli/Import.cpp +++ b/src/cli/Import.cpp @@ -60,8 +60,8 @@ int Import::execute(const QStringList& arguments) TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); const QStringList args = parser->positionalArguments(); - const QString xmlExportPath = args.at(0); - const QString dbPath = args.at(1); + const QString& xmlExportPath = args.at(0); + const QString& dbPath = args.at(1); if (QFileInfo::exists(dbPath)) { errorTextStream << QObject::tr("File %1 already exists.").arg(dbPath) << endl; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 62e67aed8..d068cdf53 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -60,7 +60,7 @@ int List::executeWithDatabase(QSharedPointer database, QSharedPointer< return EXIT_SUCCESS; } - QString groupPath = args.at(1); + const QString& groupPath = args.at(1); Group* group = database->rootGroup()->findGroupByPath(groupPath); if (!group) { errorTextStream << QObject::tr("Cannot find group %1.").arg(groupPath) << endl; diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index eeb37d803..9b574852d 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -40,7 +40,7 @@ int Locate::executeWithDatabase(QSharedPointer database, QSharedPointe { const QStringList args = parser->positionalArguments(); - QString searchTerm = args.at(1); + const QString& searchTerm = args.at(1); TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 7da1c871c..646d5d90d 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -31,6 +31,11 @@ const QCommandLineOption Show::TotpOption = QCommandLineOption(QStringList() << << "totp", QObject::tr("Show the entry's current TOTP.")); +const QCommandLineOption Show::ProtectedAttributesOption = + QCommandLineOption(QStringList() << "s" + << "show-protected", + QObject::tr("Show the protected attributes in clear text.")); + const QCommandLineOption Show::AttributesOption = QCommandLineOption( QStringList() << "a" << "attributes", @@ -46,6 +51,7 @@ Show::Show() description = QObject::tr("Show an entry's information."); options.append(Show::TotpOption); options.append(Show::AttributesOption); + options.append(Show::ProtectedAttributesOption); positionalArguments.append({QString("entry"), QObject::tr("Name of the entry to show."), QString("")}); } @@ -57,6 +63,7 @@ int Show::executeWithDatabase(QSharedPointer database, QSharedPointer< const QStringList args = parser->positionalArguments(); const QString& entryPath = args.at(1); bool showTotp = parser->isSet(Show::TotpOption); + bool showProtectedAttributes = parser->isSet(Show::ProtectedAttributesOption); QStringList attributes = parser->values(Show::AttributesOption); Entry* entry = database->rootGroup()->findEntryByPath(entryPath); @@ -78,16 +85,20 @@ int Show::executeWithDatabase(QSharedPointer database, QSharedPointer< // Iterate over the attributes and output them line-by-line. bool sawUnknownAttribute = false; - for (const QString& attribute : asConst(attributes)) { - if (!entry->attributes()->contains(attribute)) { + for (const QString& attributeName : asConst(attributes)) { + if (!entry->attributes()->contains(attributeName)) { sawUnknownAttribute = true; - errorTextStream << QObject::tr("ERROR: unknown attribute %1.").arg(attribute) << endl; + errorTextStream << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl; continue; } if (showAttributeNames) { - outputTextStream << attribute << ": "; + outputTextStream << attributeName << ": "; + } + if (entry->attributes()->isProtected(attributeName) && showAttributeNames && !showProtectedAttributes) { + outputTextStream << "PROTECTED" << endl; + } else { + outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attributeName)) << endl; } - outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl; } if (showTotp) { diff --git a/src/cli/Show.h b/src/cli/Show.h index 03700b465..bf76c6973 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -29,6 +29,7 @@ public: static const QCommandLineOption TotpOption; static const QCommandLineOption AttributesOption; + static const QCommandLineOption ProtectedAttributesOption; }; #endif // KEEPASSXC_SHOW_H diff --git a/src/core/Alloc.cpp b/src/core/Alloc.cpp index 967b4e3ef..625386a3f 100644 --- a/src/core/Alloc.cpp +++ b/src/core/Alloc.cpp @@ -23,8 +23,10 @@ #include #elif defined(Q_OS_FREEBSD) #include -#else +#elif defined(HAVE_MALLOC_H) #include +#else +#include #endif #if defined(NDEBUG) && !defined(__cpp_sized_deallocation) @@ -64,7 +66,7 @@ void operator delete(void* ptr) noexcept ::operator delete(ptr, _msize(ptr)); #elif defined(Q_OS_MACOS) ::operator delete(ptr, malloc_size(ptr)); -#elif defined(Q_OS_UNIX) +#elif defined(HAVE_MALLOC_USABLE_SIZE) ::operator delete(ptr, malloc_usable_size(ptr)); #else // whatever OS this is, give up and simply free stuff diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 944597f56..eb01f7314 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -158,7 +158,7 @@ bool Database::open(const QString& filePath, QSharedPointer m_initialized = true; emit databaseOpened(); - m_fileWatcher->start(canonicalFilePath()); + m_fileWatcher->start(canonicalFilePath(), 30, 1); setEmitModified(true); return true; @@ -234,7 +234,7 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool bool ok = performSave(canonicalFilePath, error, atomic, backup); if (ok) { setFilePath(filePath); - m_fileWatcher->start(canonicalFilePath); + m_fileWatcher->start(canonicalFilePath, 30, 1); } else { // Saving failed, don't rewatch file since it does not represent our database markAsModified(); diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp index 1b39e597d..82328832f 100644 --- a/src/core/FileWatcher.cpp +++ b/src/core/FileWatcher.cpp @@ -42,7 +42,7 @@ FileWatcher::FileWatcher(QObject* parent) m_fileIgnoreDelayTimer.setSingleShot(true); } -void FileWatcher::start(const QString& filePath, int checksumInterval) +void FileWatcher::start(const QString& filePath, int checksumIntervalSeconds, int checksumSizeKibibytes) { stop(); @@ -63,8 +63,14 @@ void FileWatcher::start(const QString& filePath, int checksumInterval) m_fileWatcher.addPath(filePath); m_filePath = filePath; + + // Handle file checksum + m_fileChecksumSizeBytes = checksumSizeKibibytes * 1024; m_fileChecksum = calculateChecksum(); - m_fileChecksumTimer.start(checksumInterval); + if (checksumIntervalSeconds > 0) { + m_fileChecksumTimer.start(checksumIntervalSeconds * 1000); + } + m_ignoreFileChange = false; } @@ -131,9 +137,12 @@ QByteArray FileWatcher::calculateChecksum() QFile file(m_filePath); if (file.open(QFile::ReadOnly)) { QCryptographicHash hash(QCryptographicHash::Sha256); - if (hash.addData(&file)) { - return hash.result(); + if (m_fileChecksumSizeBytes > 0) { + hash.addData(file.read(m_fileChecksumSizeBytes)); + } else { + hash.addData(&file); } + return hash.result(); } return {}; } diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h index 3793ae860..fea05fc84 100644 --- a/src/core/FileWatcher.h +++ b/src/core/FileWatcher.h @@ -30,7 +30,7 @@ class FileWatcher : public QObject public: explicit FileWatcher(QObject* parent = nullptr); - void start(const QString& path, int checksumInterval = 1000); + void start(const QString& path, int checksumIntervalSeconds = 0, int checksumSizeKibibytes = -1); void stop(); bool hasSameFileChecksum(); @@ -56,6 +56,7 @@ private: QTimer m_fileChangeDelayTimer; QTimer m_fileIgnoreDelayTimer; QTimer m_fileChecksumTimer; + int m_fileChecksumSizeBytes; bool m_ignoreFileChange; }; diff --git a/src/core/IconDownloader.cpp b/src/core/IconDownloader.cpp index 36047ce2a..fe346becd 100644 --- a/src/core/IconDownloader.cpp +++ b/src/core/IconDownloader.cpp @@ -90,7 +90,7 @@ void IconDownloader::setUrl(const QString& entryUrl) // searching for a match with the returned address(es). bool hostIsIp = false; QList hostAddressess = QHostInfo::fromName(fullyQualifiedDomain).addresses(); - for (auto addr : hostAddressess) { + for (const auto& addr : hostAddressess) { if (addr.toString() == fullyQualifiedDomain) { hostIsIp = true; } diff --git a/src/format/OpVaultReader.cpp b/src/format/OpVaultReader.cpp index 49d62b624..cc72653fd 100644 --- a/src/format/OpVaultReader.cpp +++ b/src/format/OpVaultReader.cpp @@ -341,6 +341,8 @@ OpVaultReader::decodeB64CompositeKeys(const QString& b64, const QByteArray& encK result->errorStr = tr("Unable to decode masterKey: %1").arg(keyKey01.errorString()); return result; } + delete result; + const QByteArray keyKey = keyKey01.getClearText(); return decodeCompositeKeys(keyKey); diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp index 81e913b0e..1c2fcb8b6 100644 --- a/src/keys/drivers/YubiKeyStub.cpp +++ b/src/keys/drivers/YubiKeyStub.cpp @@ -75,3 +75,10 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte return ERROR; } + +bool YubiKey::checkSlotIsBlocking(int slot, QString& errorMessage) +{ + Q_UNUSED(slot); + Q_UNUSED(errorMessage); + return false; +} diff --git a/src/main.cpp b/src/main.cpp index 91eb1b73e..5aff860e1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,6 +55,10 @@ int main(int argc, char** argv) #endif #endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + QGuiApplication::setDesktopFileName("org.keepassxc.KeePassXC.desktop"); +#endif + Application app(argc, argv); Application::setApplicationName("keepassxc"); Application::setApplicationVersion(KEEPASSXC_VERSION); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 288f64470..9aac1b7d8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,17 +87,7 @@ macro(add_unit_test) endif() endmacro(add_unit_test) -set(TEST_LIBRARIES - keepassx_core - ${keepassxcbrowser_LIB} - ${autotype_LIB} - Qt5::Core - Qt5::Concurrent - Qt5::Widgets - Qt5::Test - ${GCRYPT_LIBRARIES} - ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES}) +set(TEST_LIBRARIES keepassx_core Qt5::Test) set(testsupport_SOURCES modeltest.cpp @@ -108,10 +98,6 @@ set(testsupport_SOURCES add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) -if(YUBIKEY_FOUND) - set(TEST_LIBRARIES ${TEST_LIBRARIES} ${YUBIKEY_LIBRARIES}) -endif() - add_unit_test(NAME testgroup SOURCES TestGroup.cpp LIBS testsupport ${TEST_LIBRARIES}) diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 586c39be1..8a9ab50ce 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -21,9 +21,9 @@ #include "core/Bootstrap.h" #include "core/Config.h" #include "core/Global.h" -#include "core/PasswordGenerator.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "keys/drivers/YubiKey.h" #include "format/Kdbx3Reader.h" #include "format/Kdbx3Writer.h" #include "format/Kdbx4Reader.h" @@ -1682,14 +1682,15 @@ void TestCli::testShow() QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" - "Password: Password\n" + "Password: PROTECTED\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); qint64 pos = m_stdoutFile->pos(); Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); + showCmd.execute({"show", "-s", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" @@ -1697,6 +1698,17 @@ void TestCli::testShow() "URL: http://www.somesite.com/\n" "Notes: Notes\n")); + pos = m_stdoutFile->pos(); + Utils::Test::setNextPassword("a"); + showCmd.execute({"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); + m_stdoutFile->seek(pos); + QCOMPARE(m_stdoutFile->readAll(), + QByteArray("Title: Sample Entry\n" + "UserName: User Name\n" + "Password: PROTECTED\n" + "URL: http://www.somesite.com/\n" + "Notes: Notes\n")); + pos = m_stdoutFile->pos(); Utils::Test::setNextPassword("a"); showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); @@ -1704,6 +1716,13 @@ void TestCli::testShow() m_stdoutFile->readLine(); // skip password prompt QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n")); + pos = m_stdoutFile->pos(); + Utils::Test::setNextPassword("a"); + showCmd.execute({"show", "-a", "Password", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Password\n")); + pos = m_stdoutFile->pos(); Utils::Test::setNextPassword("a"); showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp index 46d254098..f31e30414 100644 --- a/tests/TestCsvParser.cpp +++ b/tests/TestCsvParser.cpp @@ -111,7 +111,7 @@ void TestCsvParser::testEmptySimple() out << ""; QVERIFY(parser->parse(file.data())); t = parser->getCsvTable(); - QVERIFY(t.size() == 0); + QVERIFY(t.isEmpty()); } void TestCsvParser::testEmptyQuoted() @@ -120,7 +120,7 @@ void TestCsvParser::testEmptyQuoted() out << "\"\""; QVERIFY(parser->parse(file.data())); t = parser->getCsvTable(); - QVERIFY(t.size() == 0); + QVERIFY(t.isEmpty()); } void TestCsvParser::testEmptyNewline() @@ -129,14 +129,14 @@ void TestCsvParser::testEmptyNewline() out << "\"\n\""; QVERIFY(parser->parse(file.data())); t = parser->getCsvTable(); - QVERIFY(t.size() == 0); + QVERIFY(t.isEmpty()); } void TestCsvParser::testEmptyFile() { QVERIFY(parser->parse(file.data())); t = parser->getCsvTable(); - QVERIFY(t.size() == 0); + QVERIFY(t.isEmpty()); } void TestCsvParser::testNewline() @@ -281,7 +281,7 @@ void TestCsvParser::testEmptyReparsing() parser->parse(nullptr); QVERIFY(parser->reparse()); t = parser->getCsvTable(); - QVERIFY(t.size() == 0); + QVERIFY(t.isEmpty()); } void TestCsvParser::testReparsing() diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index e32de2466..670e43aab 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -296,7 +296,7 @@ void TestEntryModel::testProxyModel() QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int))); modelProxy->hideColumn(0, true); QCOMPARE(modelProxy->columnCount(), 12); - QVERIFY(spyColumnRemove.size() >= 1); + QVERIFY(!spyColumnRemove.isEmpty()); int oldSpyColumnRemoveSize = spyColumnRemove.size(); modelProxy->hideColumn(0, true); @@ -318,7 +318,7 @@ void TestEntryModel::testProxyModel() QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int))); modelProxy->hideColumn(0, false); QCOMPARE(modelProxy->columnCount(), 13); - QVERIFY(spyColumnInsert.size() >= 1); + QVERIFY(!spyColumnInsert.isEmpty()); int oldSpyColumnInsertSize = spyColumnInsert.size(); modelProxy->hideColumn(0, false); diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index ae9c59894..9fc39dc64 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -1070,7 +1070,7 @@ void TestGroup::testHierarchy() QVERIFY(hierarchy.contains("group3")); hierarchy = group3->hierarchy(0); - QVERIFY(hierarchy.size() == 0); + QVERIFY(hierarchy.isEmpty()); hierarchy = group3->hierarchy(1); QVERIFY(hierarchy.size() == 1); diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index 168272bac..6a8d21c4a 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -16,5 +16,8 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) add_unit_test(NAME testgui SOURCES TestGui.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testguibrowser SOURCES TestGuiBrowser.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES}) + +if(WITH_XC_BROWSER) + add_unit_test(NAME testguibrowser SOURCES TestGuiBrowser.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) +endif()