diff --git a/client/appevents.cpp b/client/appevents.cpp index 30f0825..36a1872 100644 --- a/client/appevents.cpp +++ b/client/appevents.cpp @@ -1,23 +1,27 @@ #include "appevents.h" ClientInitEvent::ClientInitEvent() - :QEvent((Type)ClientInitId) + :QEvent(static_cast(ClientInitId)) { } ClientCloseEvent::ClientCloseEvent() - :QEvent((Type)ClientCloseId) + :QEvent(static_cast(ClientCloseId)) {} AttachDatabaseEvent::AttachDatabaseEvent() - :QEvent((Type)AttachDatabaseId) + :QEvent(static_cast(AttachDatabaseId)) {} SelectTaskEvent::SelectTaskEvent(PTask task) - :QEvent((Type)SelectTaskId), mTask(task) + :QEvent(static_cast(SelectTaskId)), mTask(task) {} PTask SelectTaskEvent::task() { return mTask; } + +UiInitEvent::UiInitEvent() + :QEvent(static_cast(UiInitId)) +{} diff --git a/client/appevents.h b/client/appevents.h index 8f7d94e..c88b2fa 100644 --- a/client/appevents.h +++ b/client/appevents.h @@ -5,38 +5,46 @@ #include "task.h" enum { - ClientInitId = 62000, - ClientCloseId = 62001, - AttachDatabaseId, - SelectTaskId + ClientInitId = 62000, + ClientCloseId = 62001, + AttachDatabaseId, + SelectTaskId, + UiInitId }; class ClientInitEvent: public QEvent { public: - ClientInitEvent(); + ClientInitEvent(); }; class ClientCloseEvent: public QEvent { public: - ClientCloseEvent(); + ClientCloseEvent(); }; class AttachDatabaseEvent: public QEvent { public: - AttachDatabaseEvent(); + AttachDatabaseEvent(); }; class SelectTaskEvent: public QEvent { public: - SelectTaskEvent(PTask task); - PTask task(); + SelectTaskEvent(PTask task); + PTask task(); protected: - PTask mTask; + PTask mTask; }; +class UiInitEvent: public QEvent +{ +public: + UiInitEvent(); +}; + + #endif // APPEVENTS_H diff --git a/client/chooka.pro b/client/chooka.pro index 5f982b7..0a3e3ab 100644 --- a/client/chooka.pro +++ b/client/chooka.pro @@ -146,3 +146,7 @@ OBJECTIVE_SOURCES += \ !include("fervor/Fervor.pri") { error("Unable to include Fervor autoupdater.") } + +!include("qtkeychain/qt5keychain.pri") { + error("Unable to include QTKeyChain library.") +} diff --git a/client/helper.cpp b/client/helper.cpp index b3ae6ff..e333ceb 100644 --- a/client/helper.cpp +++ b/client/helper.cpp @@ -300,3 +300,35 @@ bool EscapeKeyEventFilter::eventFilter(QObject *obj, QEvent * event) } return false; } + +#include "qtkeychain/keychain.h" + +QString password::load() +{ + QKeychain::ReadPasswordJob job(APPNAME); + job.setKey(KEY_PASSWORD); + job.setAutoDelete(false); + QEventLoop loop; + job.connect(&job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit())); + job.start(); + loop.exec(); + if (job.error()) + return QString(); + + return job.textData(); +} + +bool password::save(const QString& password) +{ + QKeychain::WritePasswordJob job(APPNAME); + job.setKey(KEY_PASSWORD); + job.setAutoDelete(false); + job.setTextData(password); + QEventLoop loop; + job.connect(&job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit())); + job.start(); + loop.exec(); + if (job.error()) + return false; + return true; +} diff --git a/client/helper.h b/client/helper.h index 0ef8287..cfb70ca 100644 --- a/client/helper.h +++ b/client/helper.h @@ -92,6 +92,13 @@ namespace helper void escapePressed(QObject* obj); }; + + class password + { + public: + static QString load(); + static bool save(const QString& password); + }; } #endif diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 98410e3..9db0cae 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -28,6 +28,7 @@ #include "stopworkdialog.h" #include "connectdb_widget.h" #include "openorcreatedb_widget.h" +#include "qtkeychain/keychain.h" #include #include @@ -72,8 +73,26 @@ MainWindow::MainWindow(QWidget *parent) : if (!QFile::exists(path)) askNewDbPassword(); else - askDbPassword(QString()); - + { + if (mSettings->data()[KEY_AUTOSAVE_PASSWORD].toBool()) + { + QString password = helper::password::load(); + if (password.isEmpty()) + askDbPassword(); + else + { + Storage::instance().setKey(password); + if (!Storage::instance().open()) + { + askDbPassword(tr("Invalid password, please try again.")); + } + else + QApplication::postEvent(this, new UiInitEvent()); + } + } + else + askDbPassword(QString()); + } this->setUpdatesEnabled(true); } @@ -95,28 +114,6 @@ void MainWindow::attachDatabase() QString folder = QFileInfo(path).absoluteDir().path(); Storage::instance().setPath(path); - /* - if (!QFile::exists(path)) - { - QDir().mkpath(folder); - - // Ask about new password - mNewPasswordDlg = new NewPasswordDlg(this); - connect(mNewPasswordDlg, SIGNAL(finished(int)), this, SLOT(newPasswordDialogFinished(int))); - mNewPasswordDlg->show(); - } - else - { - // See if there is stored password - if (mSettings->data()[KEY_AUTOSAVE_PASSWORD].toBool() && mSettings->data()[KEY_PASSWORD].toString() != NOPASSWORDSTRING) - password = mSettings->data()[KEY_PASSWORD].toString(); - else - { - mPasswordDlg = new PasswordDlg(this); - connect(mPasswordDlg, SIGNAL(finished(int)), this, SLOT(passwordDialogFinished(int))); - mPasswordDlg->show(); - } - }*/ this->setUpdatesEnabled(true); } @@ -399,20 +396,20 @@ void MainWindow::customEvent(QEvent *ev) { switch (ev->type()) { - case (QEvent::Type)ClientInitId: + case static_cast(ClientInitId): // Process client initialization here initClient(); break; - case (QEvent::Type)ClientCloseId: + case static_cast(ClientCloseId): close(); break; - case (QEvent::Type)AttachDatabaseId: + case static_cast(AttachDatabaseId): attachDatabase(); break; - case (QEvent::Type)SelectTaskId: + case static_cast(SelectTaskId): { QModelIndex index = mTaskTreeModel->getIndex(dynamic_cast(ev)->task()); if (index.isValid()) @@ -421,6 +418,13 @@ void MainWindow::customEvent(QEvent *ev) } break; } + + case static_cast(UiInitId): + setupMainUi(); + connectUiToDatabase(); + loadGeometry(); + break; + default: break; } @@ -443,7 +447,10 @@ void MainWindow::preferences() // Delete autosaved password if needed if (mSettings->data()[KEY_AUTOSAVE_PASSWORD].toBool() == false) { - mSettings->data()[KEY_PASSWORD] = NOPASSWORDSTRING; + // Reset password in keychain + helper::password::save(QString("")); + + //mSettings->data()[KEY_PASSWORD] = NOPASSWORDSTRING; mSettings->save(); } @@ -1618,23 +1625,19 @@ void MainWindow::trayWindowDestroyed(QObject *object) void MainWindow::onDbPasswordEntered(const QString& password) { + // Save password to keychain if needed if (mSettings->data()[KEY_AUTOSAVE_PASSWORD].toBool()) - { - mSettings->data()[KEY_PASSWORD] = password; - mSettings->save(); - } + helper::password::save(password); + // Try to open database Storage::instance().setKey(password); if (!Storage::instance().open()) { + // Ask password again if failed askDbPassword(tr("Invalid password, please try again.")); } else - { - setupMainUi(); - connectUiToDatabase(); - loadGeometry(); - } + QApplication::postEvent(this, new UiInitEvent()); } void MainWindow::onDbPasswordCancelled() @@ -1644,12 +1647,11 @@ void MainWindow::onDbPasswordCancelled() void MainWindow::onNewDbPasswordEntered(const QString& password) { + // Save password to keychain if needed if (mSettings->data()[KEY_AUTOSAVE_PASSWORD].toBool()) - { - mSettings->data()[KEY_PASSWORD] = password; - mSettings->save(); - } + helper::password::save(password); + // Configure storage Storage::instance().setKey(password); // Remove old database @@ -1661,11 +1663,7 @@ void MainWindow::onNewDbPasswordEntered(const QString& password) showFatal(tr("Failed to create new database. Exiting.")); } else - { - setupMainUi(); - connectUiToDatabase(); - loadGeometry(); - } + QApplication::postEvent(this, new UiInitEvent()); } void MainWindow::onDatabaseChanged(const QString& path) diff --git a/client/preferencesdlg.cpp b/client/preferencesdlg.cpp index 23838b7..8465570 100644 --- a/client/preferencesdlg.cpp +++ b/client/preferencesdlg.cpp @@ -2,6 +2,7 @@ #include "ui_preferencesdlg.h" #include "settings.h" #include "helper.h" +#include "storage.h" #include "platforms/hidtracker.h" #include #include @@ -73,7 +74,10 @@ void PreferencesDlg::selectDatabase() void PreferencesDlg::accepted() { - mSettings.data()[KEY_AUTOSAVE_PASSWORD] = ui->mAutosavePasswordCheckbox->isChecked(); + bool savePassword = ui->mAutosavePasswordCheckbox->isChecked(); + mSettings.data()[KEY_AUTOSAVE_PASSWORD] = savePassword; + helper::password::save(savePassword ? Storage::instance().key() : QString("")); + mSettings.data()[KEY_SHOW_SECONDS] = ui->mShowSecondsCheckbox->isChecked(); mSettings.data()[KEY_DB_FILENAME_SPECIFIED] = ui->mCustomDatabaseFileCheckbox->isChecked(); mSettings.data()[KEY_DB_FILENAME] = ui->mDatabaseLocation->text(); diff --git a/client/preferencesdlg.ui b/client/preferencesdlg.ui index 1215e84..515367f 100644 --- a/client/preferencesdlg.ui +++ b/client/preferencesdlg.ui @@ -7,7 +7,7 @@ 0 0 444 - 353 + 374 @@ -30,7 +30,7 @@ - Autosave password (requires app restart) + Save password in keychain diff --git a/client/qtkeychain/.gitignore b/client/qtkeychain/.gitignore new file mode 100644 index 0000000..8fcb5ea --- /dev/null +++ b/client/qtkeychain/.gitignore @@ -0,0 +1,50 @@ +#CMake files +CMakeCache.txt +CMakeFiles +CMakeScripts +cmake_install.cmake + +#Keychain temporary files +Qt5KeychainBuildTreeSettings.cmake +Qt5KeychainConfig.cmake +Qt5KeychainConfigVersion.cmake +QtKeychainBuildTreeSettings.cmake +QtKeychainConfig.cmake +QtKeychainConfigVersion.cmake +kwallet_interface.cpp +kwallet_interface.h +kwallet_interface.moc +moc_keychain.* +moc_keychain_p.* +moc_gnomekeyring_p.* +qkeychain_export.h +qt_Qt5Keychain.pri + +#Qt files +*_parameters +*.qm + +#General build files +Debug +Release +Makefile + +#Linux build files +libqt5keychain.* +testclient + +#Windows build files +install_manifest.txt +*.manifest +*.lib +*.exe + +#Mac build files +qtkeychain.xcodeproj +qtkeychain.build + +#Temporary files +*.sw? +*~ + + diff --git a/client/qtkeychain/CMakeLists.txt b/client/qtkeychain/CMakeLists.txt new file mode 100644 index 0000000..4449bc2 --- /dev/null +++ b/client/qtkeychain/CMakeLists.txt @@ -0,0 +1,271 @@ +cmake_minimum_required(VERSION 2.8.11) +project(qtkeychain) + +include(FindPkgConfig) + +### + +set(QTKEYCHAIN_VERSION 0.9.90) +set(QTKEYCHAIN_SOVERSION 1) + +### + +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake/Modules") +include(GNUInstallDirs) +include(GenerateExportHeader) +include(ECMPackageConfigHelpers) +include(ECMSetupVersion) +include(ECMGeneratePriFile) + +option(BUILD_WITH_QT4 "Build qtkeychain with Qt4 no matter if Qt5 was found" OFF) +option(BUILD_TEST_APPLICATION "Build test application" ON) +option(BUILD_TRANSLATIONS "Build translations" ON) +option(QTKEYCHAIN_STATIC "Build static library" OFF) + +if(CMAKE_SYSTEM_NAME STREQUAL Android) + set(ANDROID 1) +endif() + +if (WIN32) + option(USE_CREDENTIAL_STORE "Build with windows CredentialStore support" ON) + + if (USE_CREDENTIAL_STORE) + add_definitions(-DUSE_CREDENTIAL_STORE=1) + endif() +endif() + +if( NOT BUILD_WITH_QT4 ) + # try Qt5 first, and prefer that if found + find_package(Qt5Core QUIET) +endif() + +if (Qt5Core_FOUND AND NOT BUILD_WITH_QT4) + set(QTKEYCHAIN_VERSION_INFIX 5) + + if(UNIX AND NOT APPLE AND NOT ANDROID) + find_package(Qt5DBus REQUIRED) + include_directories(${Qt5DBus_INCLUDE_DIRS}) + set(QTDBUS_LIBRARIES ${Qt5DBus_LIBRARIES}) + macro(qt_add_dbus_interface) + qt5_add_dbus_interface(${ARGN}) + endmacro() + endif() + + if(BUILD_TRANSLATIONS) + find_package(Qt5LinguistTools REQUIRED) + macro(qt_add_translation) + qt5_add_translation(${ARGN}) + endmacro(qt_add_translation) + macro(qt_create_translation) + qt5_create_translation(${ARGN}) + endmacro(qt_create_translation) + endif() + + macro(qt_wrap_cpp) + qt5_wrap_cpp(${ARGN}) + endmacro() + + set(QTCORE_LIBRARIES ${Qt5Core_LIBRARIES}) + include_directories(${Qt5Core_INCLUDE_DIRS}) + + if (NOT Qt5Core_VERSION VERSION_LESS "5.7.0") + if (CMAKE_COMPILER_IS_GNUCXX) + if ((NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.7.0") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.1.0")) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.7.0") + message(FATAL_ERROR "Can't build QtKeychain using g++-${CMAKE_CXX_COMPILER_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required") + endif() + elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if (NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.3) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + else() + message(FATAL_ERROR "Can't build QtKeychain using clang++-${CMAKE_CXX_COMPILER_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required") + endif() + elseif ((CMAKE_CXX_COMPILER_ID MATCHES "MSVC") AND (MSVC_VERSION LESS 1700)) + message(FATAL_ERROR "Can't build QtKeychain using VC++-${MSVC_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required") + endif() + endif() +else() + set(QTKEYCHAIN_VERSION_INFIX "") + if(UNIX AND NOT APPLE) + find_package(Qt4 COMPONENTS QtCore QtDBus REQUIRED) + set(QTDBUS_LIBRARIES ${QT_QTDBUS_LIBRARY}) + macro(qt_add_dbus_interface) + qt4_add_dbus_interface(${ARGN}) + endmacro() + else() + find_package(Qt4 COMPONENTS QtCore REQUIRED) + endif() + include_directories(${QT_INCLUDES}) + set(QTCORE_LIBRARIES ${QT_QTCORE_LIBRARY}) + macro(qt_add_translation) + qt4_add_translation(${ARGN}) + endmacro(qt_add_translation) + macro(qt_create_translation) + qt4_create_translation(${ARGN}) + endmacro(qt_create_translation) + macro(qt_wrap_cpp) + qt4_wrap_cpp(${ARGN}) + endmacro() +endif() + + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +list(APPEND qtkeychain_LIBRARIES ${QTCORE_LIBRARIES}) +set(qtkeychain_SOURCES + keychain.cpp + qkeychain_export.h + keychain.h +) + +add_definitions( -Wall ) + +if(WIN32) + list(APPEND qtkeychain_SOURCES keychain_win.cpp) + if (NOT USE_CREDENTIAL_STORE) + list(APPEND qtkeychain_LIBRARIES crypt32) + list(APPEND qtkeychain_SOURCES plaintextstore.cpp) + endif() + #FIXME: mingw bug; otherwise getting undefined refs to RtlSecureZeroMemory there + if(MINGW) + add_definitions( -O2 ) + endif() +endif() + +if(APPLE) + if(IOS) + list(APPEND qtkeychain_SOURCES keychain_ios.cpp) + else() + list(APPEND qtkeychain_SOURCES keychain_mac.cpp) + endif() + + find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED) + list(APPEND qtkeychain_LIBRARIES ${COREFOUNDATION_LIBRARY}) + + find_library(SECURITY_LIBRARY Security REQUIRED) + list(APPEND qtkeychain_LIBRARIES ${SECURITY_LIBRARY}) +endif() + +if(UNIX AND NOT APPLE AND NOT ANDROID) + option(LIBSECRET_SUPPORT "Build with libsecret support" ON) + + if(LIBSECRET_SUPPORT) + pkg_check_modules(LIBSECRET libsecret-1) + + if (LIBSECRET_FOUND) + add_definitions(-DHAVE_LIBSECRET=1) + endif() + INCLUDE_DIRECTORIES(${LIBSECRET_INCLUDE_DIRS}) + list(APPEND qtkeychain_LIBRARIES ${LIBSECRET_LIBRARIES}) + endif() + + list(APPEND qtkeychain_SOURCES keychain_unix.cpp gnomekeyring.cpp libsecret.cpp plaintextstore.cpp) + qt_add_dbus_interface(qtkeychain_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.KWallet.xml kwallet_interface KWalletInterface) + list(APPEND qtkeychain_LIBRARIES ${QTDBUS_LIBRARIES} ) +endif() + +QT_WRAP_CPP(qtkeychain_MOC_OUTFILES keychain.h keychain_p.h gnomekeyring_p.h) + +set(qtkeychain_TR_FILES + translations/qtkeychain_de.ts + translations/qtkeychain_ro.ts +) + +file(GLOB qtkeychain_TR_SOURCES *.cpp *.h *.ui) +if ( BUILD_TRANSLATIONS ) + qt_create_translation(qtkeychain_MESSAGES ${qtkeychain_TR_SOURCES} ${qtkeychain_TR_FILES}) + qt_add_translation(qtkeychain_QM_FILES ${qtkeychain_TR_FILES}) + add_custom_target(messages DEPENDS ${qtkeychain_MESSAGES}) + add_custom_target(translations DEPENDS ${qtkeychain_QM_FILES}) + + if(NOT QT_TRANSLATIONS_DIR) + # If this directory is missing, we are in a Qt5 environment. + # Extract the qmake executable location + get_target_property(QT5_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) + # Ask Qt5 where to put the translations + execute_process( COMMAND ${QT5_QMAKE_EXECUTABLE} -query QT_INSTALL_TRANSLATIONS + OUTPUT_VARIABLE qt_translations_dir OUTPUT_STRIP_TRAILING_WHITESPACE ) + # make sure we have / and not \ as qmake gives on windows + file( TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir) + set( QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH + "The location of the Qt translations" FORCE) + endif() + + install(FILES ${qtkeychain_QM_FILES} + DESTINATION ${QT_TRANSLATIONS_DIR}) +endif( BUILD_TRANSLATIONS ) + +set(QTKEYCHAIN_TARGET_NAME qt${QTKEYCHAIN_VERSION_INFIX}keychain) +if(NOT QTKEYCHAIN_STATIC) + add_library(${QTKEYCHAIN_TARGET_NAME} SHARED ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES}) +else() + add_library(${QTKEYCHAIN_TARGET_NAME} STATIC ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES}) +endif() + +target_link_libraries(${QTKEYCHAIN_TARGET_NAME} PUBLIC ${qtkeychain_LIBRARIES}) +target_include_directories(${QTKEYCHAIN_TARGET_NAME} PUBLIC $) + +generate_export_header(${QTKEYCHAIN_TARGET_NAME} + EXPORT_FILE_NAME qkeychain_export.h + EXPORT_MACRO_NAME QKEYCHAIN_EXPORT +) + +set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES + VERSION ${QTKEYCHAIN_VERSION} + SOVERSION ${QTKEYCHAIN_SOVERSION} + MACOSX_RPATH 1 + INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + +install(FILES keychain.h ${CMAKE_CURRENT_BINARY_DIR}/qkeychain_export.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qt${QTKEYCHAIN_VERSION_INFIX}keychain/ +) + +install(TARGETS ${QTKEYCHAIN_TARGET_NAME} + EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +if(BUILD_TEST_APPLICATION) + add_executable( testclient testclient.cpp ) + target_link_libraries( testclient ${QTKEYCHAIN_TARGET_NAME}) +endif() + + +### +### CMake config file +### + +ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/QtKeychainConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake" + INSTALL_DESTINATION Qt${QTKEYCHAIN_VERSION_INFIX}Keychain) + +ecm_setup_version("${QTKEYCHAIN_VERSION}" VARIABLE_PREFIX SNORE + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake" + SOVERSION ${QTKEYCHAIN_VERSION}) + +if(UNIX AND NOT APPLE AND NOT ANDROID) + set(PRI_EXTRA_DEPS "dbus") +endif() +ecm_generate_pri_file(BASE_NAME Qt${QTKEYCHAIN_VERSION_INFIX}Keychain + LIB_NAME ${QTKEYCHAIN_TARGET_NAME} + DEPS "core ${PRI_EXTRA_DEPS}" + INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR} + FILENAME_VAR pri_filename) + +install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) + + +install(EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain" +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain +) + diff --git a/client/qtkeychain/COPYING b/client/qtkeychain/COPYING new file mode 100644 index 0000000..cca2a5c --- /dev/null +++ b/client/qtkeychain/COPYING @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/client/qtkeychain/ChangeLog b/client/qtkeychain/ChangeLog new file mode 100644 index 0000000..d04ac0b --- /dev/null +++ b/client/qtkeychain/ChangeLog @@ -0,0 +1,54 @@ +ChangeLog +========= + +version 0.9.1 (release 2018-08-20) + * Windows Credential Store: Use CRED_PERSIST_ENTERPRISE (Olivier Goffart ) + * Secret: Don't match the schema name #114 (Christian Kamm ) + * Fix qmake build on Windows (Alexander Gorishnyak ) + +version 0.9.0 (release 2018-07-13) + * Fall back on libsecret if kwallet is not available (Christian Kamm ) + * Only require QtLinguist if building translations (Victor Kropp ) + * Fix building on Windows without credential store (Dmitry Ivanov ) + * Fix Qt 4 build (Sandro Knauß ) + * Make build of test application optional (Boris Pek ) + +version 0.8.0 (release 2017-04-19) + * Buildsystem improvements (Kristofer Tingdahl , Hannah von Reth , Giuseppe D'Angelo ) + * Enable C++11 support for Qt >= 5.7 (Dmitry Ivanov ) + * Doxygen documentation ( Elvis Angelaccio ) + * Libsecret support (Armin Novak ) + * iOS support (Mathias Hasselmann ) + +version 0.7.0 (release 2016-05-23) + * Bump SO version due to 0.6 being binary-incompatible to previous releases + +version 0.6.2 (release 2016-04-04) + * KWallet: Fixes a crash when storing passwords, seen on Debian/KDE4 + +version 0.6.1 (release 2016-03-31) + * Fix KWallet not working (regressions in 0.6.0) + +version 0.6.0 (release 2016-03-18) + * Added support for the Windows Credential Store + +version 0.5.0 (release 2015-05-04) + * Added support for KWallet5 (KDE5/KF) + +version 0.4.0 (release 2014-09-01) + * KWallet: Handle case where no wallet exists yet (Liviu Cristian Mirea Ghiban ) + * Improved desktop environment detection at runtime (Daniel Molkentin ) + +version 0.3.0 (release 2014-03-13) + * Gnome Keyring supported added (Francois Ferrand ) + * Improved Qt 5 support + * KWallet: Distinguish empty passwords from non-existing entries + * KWallet: Do not use hardcoded wallet name + * German translation (Daniel Molkentin ) + * Romanian translation (Arthur Țițeică ) + +version 0.2.0: no official release + +version 0.1.0 (release 2013-01-16) + * Initial release + diff --git a/client/qtkeychain/QtKeychainConfig.cmake.in b/client/qtkeychain/QtKeychainConfig.cmake.in new file mode 100644 index 0000000..3196bff --- /dev/null +++ b/client/qtkeychain/QtKeychainConfig.cmake.in @@ -0,0 +1,22 @@ +# - Config file for the QtKeychain package +# It defines the following variables +# QTKEYCHAIN_INCLUDE_DIRS - include directories for QtKeychain +# QTKEYCHAIN_LIBRARIES - libraries to link against + +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/Qt@QTKEYCHAIN_VERSION_INFIX@KeychainLibraryDepends.cmake") + + +if("@QTKEYCHAIN_VERSION_INFIX@" STREQUAL "5") + find_dependency(Qt5Core) + + if(UNIX AND NOT APPLE) + find_dependency(Qt5DBus) + endif() +else() + find_dependency(Qt4 COMPONENTS QtCore) +endif() + +set(QTKEYCHAIN_LIBRARIES "@QTKEYCHAIN_TARGET_NAME@") +get_target_property(QTKEYCHAIN_INCLUDE_DIRS "@QTKEYCHAIN_TARGET_NAME@" INTERFACE_INCLUDE_DIRECTORIES) diff --git a/client/qtkeychain/ReadMe.markdown b/client/qtkeychain/ReadMe.markdown new file mode 120000 index 0000000..3cefeef --- /dev/null +++ b/client/qtkeychain/ReadMe.markdown @@ -0,0 +1 @@ +ReadMe.txt \ No newline at end of file diff --git a/client/qtkeychain/ReadMe.txt b/client/qtkeychain/ReadMe.txt new file mode 100644 index 0000000..6fa68b6 --- /dev/null +++ b/client/qtkeychain/ReadMe.txt @@ -0,0 +1,17 @@ +QtKeychain +========== + +QtKeychain is a Qt API to store passwords and other secret data securely. How the data is stored depends on the platform: + + * **Mac OS X:** Passwords are stored in the OS X Keychain. + + * **Linux/Unix:** If running, GNOME Keyring is used, otherwise qtkeychain tries to use KWallet (via D-Bus), if available. + + * **Windows:** By default, the Windows Credential Store is used (requires Windows 7 or newer). +Pass -DUSE_CREDENTIAL_STORE=OFF to cmake use disable it. If disabled, QtKeychain uses the Windows API function +[CryptProtectData](http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx "CryptProtectData function") +to encrypt the password with the user's logon credentials. The encrypted data is then persisted via QSettings. + +In unsupported environments QtKeychain will report an error. It will not store any data unencrypted unless explicitly requested (setInsecureFallback( true )). + +**License:** QtKeychain is available under the [Modified BSD License](http://www.gnu.org/licenses/license-list.html#ModifiedBSD). See the file COPYING for details. diff --git a/client/qtkeychain/appveyor.yml b/client/qtkeychain/appveyor.yml new file mode 100644 index 0000000..2e2804a --- /dev/null +++ b/client/qtkeychain/appveyor.yml @@ -0,0 +1,31 @@ +version: '{build}' + +build_script: + - ps: | + $ErrorActionPreference="Stop" + Import-Module $env:APPVEYOR_BUILD_FOLDER\appveyorHelp.psm1 + + Init @("ninja") + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\build\$env:APPVEYOR_PROJECT_NAME + cd $env:APPVEYOR_BUILD_FOLDER\work\build\$env:APPVEYOR_PROJECT_NAME + LogExec cmake -G"Ninja" $env:APPVEYOR_BUILD_FOLDER -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_ROOT" + + CmakeImageInstall + +test: off + +cache: + - work\install -> appveyor.yml + - C:\ProgramData\chocolatey\bin -> appveyor.yml + - C:\ProgramData\chocolatey\lib -> appveyor.yml + +environment: + QT_VER: 5.7 + + matrix: + - COMPILER: msvc2015 + - COMPILER: msvc2015_64 + - COMPILER: mingw53_32 + + + diff --git a/client/qtkeychain/appveyorHelp.psm1 b/client/qtkeychain/appveyorHelp.psm1 new file mode 100644 index 0000000..b472d0b --- /dev/null +++ b/client/qtkeychain/appveyorHelp.psm1 @@ -0,0 +1,362 @@ +Write-Host "Appveyor Helper scrips https://github.com/TheOneRing/appVeyorHelp" + +$ErrorActionPreference="Stop" + +$script:INSTALL_DIR="$env:APPVEYOR_BUILD_FOLDER\work\install" +$CMAKE_INSTALL_ROOT="`"$INSTALL_DIR`"" -replace "\\", "/" +$env:PATH="$env:PATH;$script:INSTALL_DIR" + +if(!$env:CI -eq "true") +{ + function Push-AppveyorArtifact() + { + Write-Host "Push-AppveyorArtifact $ARGS" + } + function Start-FileDownload([string] $url, [string] $out) + { + if(!$out) + { + $out = $url.SubString($url.LastIndexOf("/")) + } + wget $url -Outfile $out + } +} + +function LogExec() +{ + $OldErrorActionPreference=$ErrorActionPreference + $ErrorActionPreference="Continue" + $LastExitCode = 0 + Write-Host $Args[0], $Args[1..(($Args.Count)-1)] + & $Args[0] $Args[1..(($Args.Count)-1)] + if(!$LastExitCode -eq 0) + { + exit $LastExitCode + } + $ErrorActionPreference=$OldErrorActionPreference +} + +#Set environment variables for Visual Studio Command Prompt +#http://stackoverflow.com/questions/2124753/how-i-can-use-powershell-with-the-visual-studio-command-prompt +function BAT-CALL([string] $path, [string] $arg) +{ + Write-Host "Calling `"$path`" `"$arg`"" + cmd /c "$path" "$arg" `&`& set `|`| exit 1| + foreach { + if ($_ -match "=") { + $v = $_.split("=") + #Write-Host "ENV:\$($v[0])=$($v[1])" + set-item -force -path "ENV:\$($v[0])" -value "$($v[1])" + } + } + if($LastExitCode -eq 1) { + Write-Error "$path not found." + } +} + +function Get-QtDir() +{ + $ver = 5.5 + if($env:QT_VER) + { + $ver = $env:QT_VER + } + return "C:\Qt\$ver\$env:COMPILER\" +} + +function SETUP-QT() +{ + [string] $compiler=$env:COMPILER + $qtDir = Get-QtDir + $script:QT_BINARY_DIRS = @($qtDir) + + BAT-CALL "$qtDir\bin\qtenv2.bat" + if ($compiler.StartsWith("mingw49")) + { + #remove sh.exe from path + $env:PATH=$env:PATH -replace "C:\\Program Files \(x86\)\\Git\\bin", "" + $script:MAKE="mingw32-make" + $script:CMAKE_GENERATOR="MinGW Makefiles" + $script:STRIP=@("strip", "-s") + $script:QT_BINARY_DIRS += (Resolve-Path "$qtDir\..\..\Tools\mingw492_32\opt\") + } + elseif ($compiler.StartsWith("msvc")) + { + $arch = "x86" + if($compiler.EndsWith("64")) + { + $arch = "amd64" + } + $compilerDirs = @{ + "msvc2010" = "VS100COMNTOOLS"; + "msvc2012" = "VS110COMNTOOLS"; + "msvc2013" = "VS120COMNTOOLS"; + "msvc2015" = "VS140COMNTOOLS" + } + + $compilerVar = $compilerDirs[$compiler.Split("_")[0]] + $compilerDir = (get-item -path "env:\$($compilerVar)").Value + BAT-CALL "$compilerDir\..\..\VC\vcvarsall.bat" $arch + $script:MAKE="nmake" + $script:CMAKE_GENERATOR="NMake Makefiles" + if($arch -eq "x86") + { + $script:QT_BINARY_DIRS += ("C:\OpenSSL-Win32") + } + else + { + $script:QT_BINARY_DIRS += ("C:\OpenSSL-Win64") + } + } +} + +function Install-ChocolatelyModule([string] $module, [string[]] $myargs) +{ + Write-Host "Install chocolately package $module" + LogExec cinst $module @myargs -y +} + +function Install-CmakeGitModule([string] $url, [hashtable] $arguments) +{ + $module = $url.SubString($url.LastIndexOf("/")+1) + $module = $module.Substring(0,$module.Length - 4) + if(!$arguments.Contains("branch")) + { + $arguments["branch"] = "master" + } + if(!$arguments.Contains("buildType")) + { + $arguments["buildType"] = "Release" + } + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\build\$module + pushd $env:APPVEYOR_BUILD_FOLDER\work\git + LogExec git clone -q --depth 1 --branch ([string]$arguments["branch"]) $url $module + popd + pushd $env:APPVEYOR_BUILD_FOLDER\work\build\$module + LogExec cmake -G $script:CMAKE_GENERATOR ("-DCMAKE_BUILD_TYPE=`"{0}`"" -f [string]$arguments["buildType"]) $env:APPVEYOR_BUILD_FOLDER\work\git\$module -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_ROOT" $arguments["options"] + LogExec $script:MAKE install + popd +} + +function Init([string[]] $chocoDeps, [System.Collections.Specialized.OrderedDictionary] $cmakeModules) +{ + $script:MAKE="" + $script:CMAKE_GENERATOR="" + $script:STRIP=$null + + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\image | Out-Null + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\build | Out-Null + + SETUP-QT + + if($chocoDeps -contains "ninja") { + $script:CMAKE_GENERATOR="Ninja" + $script:MAKE="ninja" + } + + if ( !(Test-Path "$env:APPVEYOR_BUILD_FOLDER\work\install" ) ) + { + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\install | Out-Null + mkdir -Force $env:APPVEYOR_BUILD_FOLDER\work\git | Out-Null + + foreach($module in $chocoDeps) { + if($module -eq "nsis") + { + Install-ChocolatelyModule "nsis.portable" @("-pre") + continue + } + Install-ChocolatelyModule $module + } + + foreach($key in $cmakeModules.Keys) { + Install-CmakeGitModule $key $cmakeModules[$key] + } + + [string] $compiler=$env:COMPILER + if($compiler.StartsWith("msvc")) + { + Write-Host "Downloading vcredist.exe" + if ($compiler.StartsWith("msvc2015")) + { + if($compiler.EndsWith("64")) + { + Start-FileDownload https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe $env:APPVEYOR_BUILD_FOLDER\work\install\vcredist.exe + } + else + { + Start-FileDownload https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x86.exe $env:APPVEYOR_BUILD_FOLDER\work\install\vcredist.exe + } + } + else + { + if($compiler.EndsWith("64")) + { + Start-FileDownload http://download.microsoft.com/download/2/E/6/2E61CFA4-993B-4DD4-91DA-3737CD5CD6E3/vcredist_x64.exe $env:APPVEYOR_BUILD_FOLDER\work\install\vcredist.exe + } + else + { + Start-FileDownload http://download.microsoft.com/download/2/E/6/2E61CFA4-993B-4DD4-91DA-3737CD5CD6E3/vcredist_x86.exe $env:APPVEYOR_BUILD_FOLDER\work\install\vcredist.exe + } + } + } + } +} + +function relativePath([string] $root, [string] $path) +{ + pushd $root + $out = Resolve-Path -Relative $path + popd + return $out +} + +function StripFile([string] $name) +{ + if($script:STRIP) { + if( $name.EndsWith(".dll") -or $name.EndsWith(".exe")) + { + Write-Host "strip file $name" + LogExec @script:STRIP $name + } + } +} + +function Get-DeployImageName() +{ + $version = Get-Version + if($env:APPVEYOR_REPO_TAG -eq "true") { + return "$env:APPVEYOR_PROJECT_NAME`_$version`_Qt$env:QT_VER`_$env:COMPILER" + }else{ + return "$env:APPVEYOR_PROJECT_NAME`_$env:APPVEYOR_REPO_BRANCH`_$version`_Qt$env:QT_VER`_$env:COMPILER" + } +} + +function Get-Version() +{ + if($env:APPVEYOR_REPO_TAG -eq "true") { + return $env:APPVEYOR_REPO_TAG_NAME + }else{ + $commit = ([string]$env:APPVEYOR_REPO_COMMIT).SubString(0,6) + return $commit + } +} + +function CmakeImageInstall() +{ + $imageName = Get-DeployImageName + $destDir = "$env:APPVEYOR_BUILD_FOLDER\work\cmakeDeployImage\$imageName" + $env:DESTDIR = $destDir + LogExec $script:MAKE install + $env:DESTDIR = $null + if(!$LastExitCode -eq 0) + { + Write-Error "Build Failed" + } + $env:DESTDIR=$null + $prefix=$script:INSTALL_DIR + if( $prefix.substring(1,1) -eq ":") + { + $prefix=$prefix.substring(3) + } + Write-Host "move $destDir\$prefix to $destDir" + mv -Force "$destDir\$prefix\*" "$destDir" + $rootLeftOver = $prefix.substring(0, $prefix.indexOf("\")) + rm -Recurse "$destDir\$rootLeftOver" +} + +function CreateDeployImage([string[]] $whiteList, [string[]] $blackList) +{ + $imageName = Get-DeployImageName + $deployPath = "$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName" + + function copyWithWhitelist([string] $root) + { + $files = ls $root -Recurse + foreach($fileName in $files.FullName) + { + $relPath = (relativePath $root $fileName).SubString(2) + if($whiteList | Where {$relPath -match $_}) + { + if($blackList | Where {$relPath -match $_}) + { + continue + } + if(!(Test-Path $deployPath\$relPath)) + { + Write-Host "copy $fileName to $deployPath\$relPath" + mkdir -Force (Split-Path -Parent $deployPath\$relPath) | Out-Null + cp -Force $fileName $deployPath\$relPath + StripFile $deployPath\$relPath + } + } + } + } + Write-Host "CreateDeployImage $imageName" + mkdir $deployPath | Out-Null + + copyWithWhitelist "$env:APPVEYOR_BUILD_FOLDER\work\cmakeDeployImage\$imageName" + copyWithWhitelist "$env:APPVEYOR_BUILD_FOLDER\work\install\" + foreach($folder in $script:QT_BINARY_DIRS) + { + copyWithWhitelist $folder + } + Write-Host "Deploy path $deployPath" + return $deployPath +} + +function 7ZipDeployImage() +{ + $imageName = Get-DeployImageName + LogExec 7za a "$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName.7z" "$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName" + Push-AppveyorArtifact "$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName.7z" +} + +function NsisDeployImage([string] $scriptName) +{ + $imageName = Get-DeployImageName + $installerName = "$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName.exe" + $version = Get-Version + if(([string]$env:COMPILER).StartsWith("msvc")) + { + $redist = "$env:APPVEYOR_BUILD_FOLDER\work\install\vcredist.exe" + }else{ + $redist = "none" + } + if($env:COMPILER.EndsWith("64")) + { + $defaultinstdir = "`$PROGRAMFILES64" + }else{ + $defaultinstdir = "`$PROGRAMFILES" + } + LogExec makensis.exe /DgitDir=$env:APPVEYOR_BUILD_FOLDER /Dsetupname=$installerName /Dcaption=$imageName /Dversion=$version /Dcompiler=$env:COMPILER /Dvcredist=$redist /Ddefaultinstdir=$defaultinstdir /Dsrcdir=$env:APPVEYOR_BUILD_FOLDER\work\deployImage\$imageName $scriptName + Push-AppveyorArtifact $installerName +} + +# based on http://thesurlyadmin.com/2013/01/07/remove-empty-directories-recursively/ +function DeleteEmptyFodlers([string] $root) +{ + $Folders = @() + foreach($Folder in (Get-ChildItem -Path $root -Recurse -Directory)) + { + $Folders += New-Object PSObject -Property @{ + Object = $Folder + Depth = ($Folder.FullName.Split("\")).Count + } + } + $Folders = $Folders | Sort Depth -Descending + + foreach($Folder in $Folders) + { + If ($Folder.Object.GetFileSystemInfos().Count -eq 0) + { + Write-Host "Delete empty dir:" $Folder.Object.FullName + Remove-Item -Path $Folder.Object.FullName -Force + } + } + +} + +Write-Host "CMAKE_INSTALL_ROOT: $CMAKE_INSTALL_ROOT" +Write-Host "Image-Name: ", (Get-DeployImageName) + +Export-ModuleMember -Function @("Init","CmakeImageInstall", "CreateDeployImage", "LogExec", "7ZipDeployImage", "NsisDeployImage", "DeleteEmptyFodlers") -Variable @("CMAKE_INSTALL_ROOT") diff --git a/client/qtkeychain/cmake/Modules/ECMGeneratePriFile.cmake b/client/qtkeychain/cmake/Modules/ECMGeneratePriFile.cmake new file mode 100644 index 0000000..8e00c10 --- /dev/null +++ b/client/qtkeychain/cmake/Modules/ECMGeneratePriFile.cmake @@ -0,0 +1,198 @@ +#.rst: +# ECMGeneratePriFile +# ------------------ +# +# Generate a ``.pri`` file for the benefit of qmake-based projects. +# +# As well as the function below, this module creates the cache variable +# ``ECM_MKSPECS_INSTALL_DIR`` and sets the default value to ``mkspecs/modules``. +# This assumes Qt and the current project are both installed to the same +# non-system prefix. Packagers who use ``-DCMAKE_INSTALL_PREFIX=/usr`` will +# certainly want to set ``ECM_MKSPECS_INSTALL_DIR`` to something like +# ``share/qt5/mkspecs/modules``. +# +# The main thing is that this should be the ``modules`` subdirectory of either +# the default qmake ``mkspecs`` directory or of a directory that will be in the +# ``$QMAKEPATH`` environment variable when qmake is run. +# +# :: +# +# ecm_generate_pri_file(BASE_NAME +# LIB_NAME +# [DEPS " [ [...]]"] +# [FILENAME_VAR ] +# [INCLUDE_INSTALL_DIR ] +# [LIB_INSTALL_DIR ]) +# +# If your CMake project produces a Qt-based library, you may expect there to be +# applications that wish to use it that use a qmake-based build system, rather +# than a CMake-based one. Creating a ``.pri`` file will make use of your +# library convenient for them, in much the same way that CMake config files make +# things convenient for CMake-based applications. +# +# ecm_generate_pri_file() generates just such a file. It requires the +# ``PROJECT_VERSION_STRING`` variable to be set. This is typically set by +# :module:`ECMSetupVersion`, although the project() command in CMake 3.0.0 and +# later can also set this. +# +# BASE_NAME specifies the name qmake project (.pro) files should use to refer to +# the library (eg: KArchive). LIB_NAME is the name of the actual library to +# link to (ie: the first argument to add_library()). DEPS is a space-separated +# list of the base names of other libraries (for Qt libraries, use the same +# names you use with the ``QT`` variable in a qmake project file, such as "core" +# for QtCore). FILENAME_VAR specifies the name of a variable to store the path +# to the generated file in. +# +# INCLUDE_INSTALL_DIR is the path (relative to ``CMAKE_INSTALL_PREFIX``) that +# include files will be installed to. It defaults to +# ``${INCLUDE_INSTALL_DIR}/`` if the ``INCLUDE_INSTALL_DIR`` variable +# is set. If that variable is not set, the ``CMAKE_INSTALL_INCLUDEDIR`` variable +# is used instead, and if neither are set ``include`` is used. LIB_INSTALL_DIR +# operates similarly for the installation location for libraries; it defaults to +# ``${LIB_INSTALL_DIR}``, ``${CMAKE_INSTALL_LIBDIR}`` or ``lib``, in that order. +# +# Example usage: +# +# .. code-block:: cmake +# +# ecm_generate_pri_file( +# BASE_NAME KArchive +# LIB_NAME KF5KArchive +# DEPS "core" +# FILENAME_VAR pri_filename +# ) +# install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) +# +# A qmake-based project that wished to use this would then do:: +# +# QT += KArchive +# +# in their ``.pro`` file. +# +# Since pre-1.0.0. + +#============================================================================= +# Copyright 2014 David Faure +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Replicate the logic from KDEInstallDirs.cmake as we can't depend on it +# Ask qmake if we're using the same prefix as Qt +set(_askqmake OFF) +if(NOT DEFINED KDE_INSTALL_USE_QT_SYS_PATHS) + include(ECMQueryQmake) + query_qmake(qt_install_prefix_dir QT_INSTALL_PREFIX) + if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}") + set(_askqmake ON) + endif() +endif() + +if(KDE_INSTALL_USE_QT_SYS_PATHS OR _askqmake) + include(ECMQueryQmake) + query_qmake(qt_host_data_dir QT_HOST_DATA) + set(ECM_MKSPECS_INSTALL_DIR ${qt_host_data_dir}/mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.") +else() + set(ECM_MKSPECS_INSTALL_DIR mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.") +endif() + +function(ECM_GENERATE_PRI_FILE) + set(options ) + set(oneValueArgs BASE_NAME LIB_NAME DEPS FILENAME_VAR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR) + set(multiValueArgs ) + + cmake_parse_arguments(EGPF "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(EGPF_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to ECM_GENERATE_PRI_FILE(): \"${EGPF_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT EGPF_BASE_NAME) + message(FATAL_ERROR "Required argument BASE_NAME missing in ECM_GENERATE_PRI_FILE() call") + endif() + if(NOT EGPF_LIB_NAME) + message(FATAL_ERROR "Required argument LIB_NAME missing in ECM_GENERATE_PRI_FILE() call") + endif() + if(NOT PROJECT_VERSION_STRING) + message(FATAL_ERROR "Required variable PROJECT_VERSION_STRING not set before ECM_GENERATE_PRI_FILE() call. Did you call ecm_setup_version?") + endif() + if(NOT EGPF_INCLUDE_INSTALL_DIR) + if(INCLUDE_INSTALL_DIR) + set(EGPF_INCLUDE_INSTALL_DIR "${INCLUDE_INSTALL_DIR}/${EGPF_BASE_NAME}") + elseif(CMAKE_INSTALL_INCLUDEDIR) + set(EGPF_INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_INCLUDEDIR}/${EGPF_BASE_NAME}") + else() + set(EGPF_INCLUDE_INSTALL_DIR "include/${EGPF_BASE_NAME}") + endif() + endif() + if(NOT EGPF_LIB_INSTALL_DIR) + if(LIB_INSTALL_DIR) + set(EGPF_LIB_INSTALL_DIR "${LIB_INSTALL_DIR}") + elseif(CMAKE_INSTALL_LIBDIR) + set(EGPF_LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") + else() + set(EGPF_LIB_INSTALL_DIR "lib") + endif() + endif() + + string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" PROJECT_VERSION_MAJOR "${PROJECT_VERSION_STRING}") + string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" PROJECT_VERSION_MINOR "${PROJECT_VERSION_STRING}") + string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" PROJECT_VERSION_PATCH "${PROJECT_VERSION_STRING}") + + set(PRI_TARGET_BASENAME ${EGPF_BASE_NAME}) + set(PRI_TARGET_LIBNAME ${EGPF_LIB_NAME}) + set(PRI_TARGET_QTDEPS ${EGPF_DEPS}) + if(IS_ABSOLUTE "${EGPF_INCLUDE_INSTALL_DIR}") + set(PRI_TARGET_INCLUDES "${EGPF_INCLUDE_INSTALL_DIR}") + else() + set(PRI_TARGET_INCLUDES "${CMAKE_INSTALL_PREFIX}/${EGPF_INCLUDE_INSTALL_DIR}") + endif() + if(IS_ABSOLUTE "${EGPF_LIB_INSTALL_DIR}") + set(PRI_TARGET_LIBS "${EGPF_LIB_INSTALL_DIR}") + else() + set(PRI_TARGET_LIBS "${CMAKE_INSTALL_PREFIX}/${EGPF_LIB_INSTALL_DIR}") + endif() + set(PRI_TARGET_DEFINES "") + + set(PRI_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/qt_${PRI_TARGET_BASENAME}.pri) + if (EGPF_FILENAME_VAR) + set(${EGPF_FILENAME_VAR} ${PRI_FILENAME} PARENT_SCOPE) + endif() + + file(GENERATE + OUTPUT ${PRI_FILENAME} + CONTENT + "QT.${PRI_TARGET_BASENAME}.VERSION = ${PROJECT_VERSION_STRING} +QT.${PRI_TARGET_BASENAME}.MAJOR_VERSION = ${PROJECT_VERSION_MAJOR} +QT.${PRI_TARGET_BASENAME}.MINOR_VERSION = ${PROJECT_VERSION_MINOR} +QT.${PRI_TARGET_BASENAME}.PATCH_VERSION = ${PROJECT_VERSION_PATCH} +QT.${PRI_TARGET_BASENAME}.name = ${PRI_TARGET_LIBNAME} +QT.${PRI_TARGET_BASENAME}.module = ${PRI_TARGET_LIBNAME} +QT.${PRI_TARGET_BASENAME}.defines = ${PRI_TARGET_DEFINES} +QT.${PRI_TARGET_BASENAME}.includes = ${PRI_TARGET_INCLUDES} +QT.${PRI_TARGET_BASENAME}.private_includes = +QT.${PRI_TARGET_BASENAME}.libs = ${PRI_TARGET_LIBS} +QT.${PRI_TARGET_BASENAME}.depends = ${PRI_TARGET_QTDEPS} +" + ) +endfunction() diff --git a/client/qtkeychain/cmake/Modules/ECMPackageConfigHelpers.cmake b/client/qtkeychain/cmake/Modules/ECMPackageConfigHelpers.cmake new file mode 100644 index 0000000..a118fea --- /dev/null +++ b/client/qtkeychain/cmake/Modules/ECMPackageConfigHelpers.cmake @@ -0,0 +1,223 @@ +#.rst: +# ECMPackageConfigHelpers +# ----------------------- +# +# Helper macros for generating CMake package config files. +# +# ``write_basic_package_version_file()`` is the same as the one provided by the +# `CMakePackageConfigHelpers +# `_ +# module in CMake; see that module's documentation for +# more information. +# +# :: +# +# ecm_configure_package_config_file( +# INSTALL_DESTINATION +# [PATH_VARS [ [...]] +# [NO_SET_AND_CHECK_MACRO] +# [NO_CHECK_REQUIRED_COMPONENTS_MACRO]) +# +# +# This behaves in the same way as configure_package_config_file() from CMake +# 2.8.12, except that it adds an extra helper macro: find_dependency(). It is +# highly recommended that you read the `documentation for +# CMakePackageConfigHelpers +# `_ +# for more information, particularly with regard to the PATH_VARS argument. +# +# Note that there is no argument that will disable the find_dependency() macro; +# if you do not require this macro, you should use +# ``configure_package_config_file`` from the CMakePackageConfigHelpers module. +# +# CMake 3.0 includes a CMakeFindDependencyMacro module that provides the +# find_dependency() macro (which you can ``include()`` in your package config +# file), so this file is only useful for projects wishing to provide config +# files that will work with CMake 2.8.12. +# +# Additional Config File Macros +# ============================= +# +# :: +# +# find_dependency( [ [EXACT]]) +# +# find_dependency() should be used instead of find_package() to find package +# dependencies. It forwards the correct parameters for EXACT, QUIET and +# REQUIRED which were passed to the original find_package() call. It also sets +# an informative diagnostic message if the dependency could not be found. +# +# Since pre-1.0.0. + +#============================================================================= +# Copyright 2014 Alex Merry +# Copyright 2013 Stephen Kelly +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(${CMAKE_ROOT}/Modules/CMakePackageConfigHelpers.cmake) + +set(_ecm_package_config_helpers_included TRUE) + +if(NOT CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.13) + message(AUTHOR_WARNING "Your project already requires a version of CMake that includes the find_dependency macro via the CMakeFindDependencyMacro module. You should use CMakePackageConfigHelpers instead of ECMPackageConfigHelpers.") +endif() + +function(ECM_CONFIGURE_PACKAGE_CONFIG_FILE _inputFile _outputFile) + set(options NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) + set(oneValueArgs INSTALL_DESTINATION ) + set(multiValueArgs PATH_VARS ) + + cmake_parse_arguments(CCF "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(CCF_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to CONFIGURE_PACKAGE_CONFIG_FILE(): \"${CCF_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT CCF_INSTALL_DESTINATION) + message(FATAL_ERROR "No INSTALL_DESTINATION given to CONFIGURE_PACKAGE_CONFIG_FILE()") + endif() + + if(IS_ABSOLUTE "${CCF_INSTALL_DESTINATION}") + set(absInstallDir "${CCF_INSTALL_DESTINATION}") + else() + set(absInstallDir "${CMAKE_INSTALL_PREFIX}/${CCF_INSTALL_DESTINATION}") + endif() + + file(RELATIVE_PATH PACKAGE_RELATIVE_PATH "${absInstallDir}" "${CMAKE_INSTALL_PREFIX}" ) + + foreach(var ${CCF_PATH_VARS}) + if(NOT DEFINED ${var}) + message(FATAL_ERROR "Variable ${var} does not exist") + else() + if(IS_ABSOLUTE "${${var}}") + string(REPLACE "${CMAKE_INSTALL_PREFIX}" "\${PACKAGE_PREFIX_DIR}" + PACKAGE_${var} "${${var}}") + else() + set(PACKAGE_${var} "\${PACKAGE_PREFIX_DIR}/${${var}}") + endif() + endif() + endforeach() + + get_filename_component(inputFileName "${_inputFile}" NAME) + + set(PACKAGE_INIT " +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() (ECM variant) ####### +####### Any changes to this file will be overwritten by the next CMake run ####### +####### The input file was ${inputFileName} ####### + +get_filename_component(PACKAGE_PREFIX_DIR \"\${CMAKE_CURRENT_LIST_DIR}/${PACKAGE_RELATIVE_PATH}\" ABSOLUTE) +") + + if("${absInstallDir}" MATCHES "^(/usr)?/lib(64)?/.+") + # Handle "/usr move" symlinks created by some Linux distros. + set(PACKAGE_INIT "${PACKAGE_INIT} +# Use original install prefix when loaded through a \"/usr move\" +# cross-prefix symbolic link such as /lib -> /usr/lib. +get_filename_component(_realCurr \"\${CMAKE_CURRENT_LIST_DIR}\" REALPATH) +get_filename_component(_realOrig \"${absInstallDir}\" REALPATH) +if(_realCurr STREQUAL _realOrig) + set(PACKAGE_PREFIX_DIR \"${CMAKE_INSTALL_PREFIX}\") +endif() +unset(_realOrig) +unset(_realCurr) +") + endif() + + if(NOT CCF_NO_SET_AND_CHECK_MACRO) + set(PACKAGE_INIT "${PACKAGE_INIT} +macro(set_and_check _var _file) + set(\${_var} \"\${_file}\") + if(NOT EXISTS \"\${_file}\") + message(FATAL_ERROR \"File or directory \${_file} referenced by variable \${_var} does not exist !\") + endif() +endmacro() + +include(CMakeFindDependencyMacro OPTIONAL RESULT_VARIABLE _CMakeFindDependencyMacro_FOUND) + +if (NOT _CMakeFindDependencyMacro_FOUND) + macro(find_dependency dep) + if (NOT \${dep}_FOUND) + + set(ecm_fd_version) + if (\${ARGC} GREATER 1) + set(ecm_fd_version \${ARGV1}) + endif() + set(ecm_fd_exact_arg) + if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT) + set(ecm_fd_exact_arg EXACT) + endif() + set(ecm_fd_quiet_arg) + if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(ecm_fd_quiet_arg QUIET) + endif() + set(ecm_fd_required_arg) + if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + set(ecm_fd_required_arg REQUIRED) + endif() + + find_package(\${dep} \${ecm_fd_version} + \${ecm_fd_exact_arg} + \${ecm_fd_quiet_arg} + \${ecm_fd_required_arg} + ) + + if (NOT \${dep}_FOUND) + set(\${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE \"\${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency \${dep} could not be found.\") + set(\${CMAKE_FIND_PACKAGE_NAME}_FOUND False) + return() + endif() + + set(ecm_fd_version) + set(ecm_fd_required_arg) + set(ecm_fd_quiet_arg) + set(ecm_fd_exact_arg) + endif() + endmacro() +endif() + +") + endif() + + + if(NOT CCF_NO_CHECK_REQUIRED_COMPONENTS_MACRO) + set(PACKAGE_INIT "${PACKAGE_INIT} +macro(check_required_components _NAME) + foreach(comp \${\${_NAME}_FIND_COMPONENTS}) + if(NOT \${_NAME}_\${comp}_FOUND) + if(\${_NAME}_FIND_REQUIRED_\${comp}) + set(\${_NAME}_FOUND FALSE) + endif() + endif() + endforeach() +endmacro() +") + endif() + + set(PACKAGE_INIT "${PACKAGE_INIT} +####################################################################################") + + configure_file("${_inputFile}" "${_outputFile}" @ONLY) + +endfunction() diff --git a/client/qtkeychain/cmake/Modules/ECMQueryQmake.cmake b/client/qtkeychain/cmake/Modules/ECMQueryQmake.cmake new file mode 100644 index 0000000..fa0949d --- /dev/null +++ b/client/qtkeychain/cmake/Modules/ECMQueryQmake.cmake @@ -0,0 +1,32 @@ +find_package(Qt5Core QUIET) + +if (Qt5Core_FOUND) + set(_qmake_executable_default "qmake-qt5") +endif () +if (TARGET Qt5::qmake) + get_target_property(_qmake_executable_default Qt5::qmake LOCATION) +endif() +set(QMAKE_EXECUTABLE ${_qmake_executable_default} + CACHE FILEPATH "Location of the Qt5 qmake executable") + +# This is not public API (yet)! +function(query_qmake result_variable qt_variable) + if(NOT QMAKE_EXECUTABLE) + set(${result_variable} "" PARENT_SCOPE) + message(WARNING "Should specify a qmake Qt5 binary. Can't check ${qt_variable}") + return() + endif() + execute_process( + COMMAND ${QMAKE_EXECUTABLE} -query "${qt_variable}" + RESULT_VARIABLE return_code + OUTPUT_VARIABLE output + ) + if(return_code EQUAL 0) + string(STRIP "${output}" output) + file(TO_CMAKE_PATH "${output}" output_path) + set(${result_variable} "${output_path}" PARENT_SCOPE) + else() + message(WARNING "Failed call: ${QMAKE_EXECUTABLE} -query \"${qt_variable}\"") + message(FATAL_ERROR "QMake call failed: ${return_code}") + endif() +endfunction() diff --git a/client/qtkeychain/cmake/Modules/ECMSetupVersion.cmake b/client/qtkeychain/cmake/Modules/ECMSetupVersion.cmake new file mode 100644 index 0000000..f5a3cce --- /dev/null +++ b/client/qtkeychain/cmake/Modules/ECMSetupVersion.cmake @@ -0,0 +1,223 @@ +#.rst: +# ECMSetupVersion +# --------------- +# +# Handle library version information. +# +# :: +# +# ecm_setup_version( +# VARIABLE_PREFIX +# [SOVERSION ] +# [VERSION_HEADER ] +# [PACKAGE_VERSION_FILE [COMPATIBILITY ]] ) +# +# This parses a version string and sets up a standard set of version variables. +# It can optionally also create a C version header file and a CMake package +# version file to install along with the library. +# +# If the ```` argument is of the form ``..`` +# (or ``...``), The following CMake variables are +# set:: +# +# _VERSION_MAJOR - +# _VERSION_MINOR - +# _VERSION_PATCH - +# _VERSION - +# _VERSION_STRING - (for compatibility: use _VERSION instead) +# _SOVERSION - , or if SOVERSION was not given +# +# If CMake policy CMP0048 is not NEW, the following CMake variables will also +# be set: +# +# PROJECT_VERSION_MAJOR - +# PROJECT_VERSION_MINOR - +# PROJECT_VERSION_PATCH - +# PROJECT_VERSION - +# PROJECT_VERSION_STRING - (for compatibility: use PROJECT_VERSION instead) +# +# If the VERSION_HEADER option is used, a simple C header is generated with the +# given filename. If filename is a relative path, it is interpreted as relative +# to CMAKE_CURRENT_BINARY_DIR. The generated header contains the following +# macros:: +# +# _VERSION_MAJOR - as an integer +# _VERSION_MINOR - as an integer +# _VERSION_PATCH - as an integer +# _VERSION_STRING - as a C string +# _VERSION - the version as an integer +# +# ``_VERSION`` has ```` in the bottom 8 bits, ```` in the +# next 8 bits and ```` in the remaining bits. Note that ```` and +# ```` must be less than 256. +# +# If the PACKAGE_VERSION_FILE option is used, a simple CMake package version +# file is created using the write_basic_package_version_file() macro provided by +# CMake. It should be installed in the same location as the Config.cmake file of +# the library so that it can be found by find_package(). If the filename is a +# relative path, it is interpreted as relative to CMAKE_CURRENT_BINARY_DIR. The +# optional COMPATIBILITY option is forwarded to +# write_basic_package_version_file(), and defaults to AnyNewerVersion. +# +# If CMake policy CMP0048 is NEW, an alternative form of the command is +# available:: +# +# ecm_setup_version(PROJECT +# [VARIABLE_PREFIX ] +# [SOVERSION ] +# [VERSION_HEADER ] +# [PACKAGE_VERSION_FILE ] ) +# +# This will use the version information set by the project() command. +# VARIABLE_PREFIX defaults to the project name. Note that PROJECT must be the +# first argument. In all other respects, it behaves like the other form of the +# command. +# +# Since pre-1.0.0. +# +# COMPATIBLITY option available since 1.6.0. + +#============================================================================= +# Copyright 2014 Alex Merry +# Copyright 2012 Alexander Neundorf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(CMakePackageConfigHelpers) + +# save the location of the header template while CMAKE_CURRENT_LIST_DIR +# has the value we want +set(_ECM_SETUP_VERSION_HEADER_TEMPLATE "${CMAKE_CURRENT_LIST_DIR}/ECMVersionHeader.h.in") + +function(ecm_setup_version _version) + set(options ) + set(oneValueArgs VARIABLE_PREFIX SOVERSION VERSION_HEADER PACKAGE_VERSION_FILE COMPATIBILITY) + set(multiValueArgs ) + + cmake_parse_arguments(ESV "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(ESV_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to ECM_SETUP_VERSION(): \"${ESV_UNPARSED_ARGUMENTS}\"") + endif() + + set(project_manages_version FALSE) + set(use_project_version FALSE) + # CMP0048 only exists in CMake 3.0.0 and later + if(CMAKE_VERSION VERSION_LESS 3.0.0) + set(project_version_policy "OLD") + else() + cmake_policy(GET CMP0048 project_version_policy) + endif() + if(project_version_policy STREQUAL "NEW") + set(project_manages_version TRUE) + if(_version STREQUAL "PROJECT") + set(use_project_version TRUE) + endif() + elseif(_version STREQUAL "PROJECT") + message(FATAL_ERROR "ecm_setup_version given PROJECT argument, but CMP0048 is not NEW") + endif() + + set(should_set_prefixed_vars TRUE) + if(NOT ESV_VARIABLE_PREFIX) + if(use_project_version) + set(ESV_VARIABLE_PREFIX "${PROJECT_NAME}") + set(should_set_prefixed_vars FALSE) + else() + message(FATAL_ERROR "Required argument PREFIX missing in ECM_SETUP_VERSION() call") + endif() + endif() + + if(use_project_version) + set(_version "${PROJECT_VERSION}") + set(_major "${PROJECT_VERSION_MAJOR}") + set(_minor "${PROJECT_VERSION_MINOR}") + set(_patch "${PROJECT_VERSION_PATCH}") + else() + string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major "${_version}") + string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor "${_version}") + string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch "${_version}") + endif() + + if(NOT ESV_SOVERSION) + set(ESV_SOVERSION ${_major}) + endif() + + if(should_set_prefixed_vars) + set(${ESV_VARIABLE_PREFIX}_VERSION "${_version}") + set(${ESV_VARIABLE_PREFIX}_VERSION_MAJOR ${_major}) + set(${ESV_VARIABLE_PREFIX}_VERSION_MINOR ${_minor}) + set(${ESV_VARIABLE_PREFIX}_VERSION_PATCH ${_patch}) + endif() + + set(${ESV_VARIABLE_PREFIX}_SOVERSION ${ESV_SOVERSION}) + + if(NOT project_manages_version) + set(PROJECT_VERSION "${_version}") + set(PROJECT_VERSION_MAJOR "${_major}") + set(PROJECT_VERSION_MINOR "${_minor}") + set(PROJECT_VERSION_PATCH "${_patch}") + endif() + + # compat + set(PROJECT_VERSION_STRING "${PROJECT_VERSION}") + set(${ESV_VARIABLE_PREFIX}_VERSION_STRING "${${ESV_VARIABLE_PREFIX}_VERSION}") + + if(ESV_VERSION_HEADER) + set(HEADER_PREFIX "${ESV_VARIABLE_PREFIX}") + set(HEADER_VERSION "${_version}") + set(HEADER_VERSION_MAJOR "${_major}") + set(HEADER_VERSION_MINOR "${_minor}") + set(HEADER_VERSION_PATCH "${_patch}") + configure_file("${_ECM_SETUP_VERSION_HEADER_TEMPLATE}" "${ESV_VERSION_HEADER}") + endif() + + if(ESV_PACKAGE_VERSION_FILE) + if(NOT ESV_COMPATIBILITY) + set(ESV_COMPATIBILITY AnyNewerVersion) + endif() + write_basic_package_version_file("${ESV_PACKAGE_VERSION_FILE}" VERSION ${_version} COMPATIBILITY ${ESV_COMPATIBILITY}) + endif() + + if(should_set_prefixed_vars) + set(${ESV_VARIABLE_PREFIX}_VERSION_MAJOR "${${ESV_VARIABLE_PREFIX}_VERSION_MAJOR}" PARENT_SCOPE) + set(${ESV_VARIABLE_PREFIX}_VERSION_MINOR "${${ESV_VARIABLE_PREFIX}_VERSION_MINOR}" PARENT_SCOPE) + set(${ESV_VARIABLE_PREFIX}_VERSION_PATCH "${${ESV_VARIABLE_PREFIX}_VERSION_PATCH}" PARENT_SCOPE) + set(${ESV_VARIABLE_PREFIX}_VERSION "${${ESV_VARIABLE_PREFIX}_VERSION}" PARENT_SCOPE) + endif() + + # always set the soversion + set(${ESV_VARIABLE_PREFIX}_SOVERSION "${${ESV_VARIABLE_PREFIX}_SOVERSION}" PARENT_SCOPE) + + if(NOT project_manages_version) + set(PROJECT_VERSION "${PROJECT_VERSION}" PARENT_SCOPE) + set(PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}" PARENT_SCOPE) + set(PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR}" PARENT_SCOPE) + set(PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}" PARENT_SCOPE) + endif() + + # always set the compatibility variables + set(PROJECT_VERSION_STRING "${PROJECT_VERSION_STRING}" PARENT_SCOPE) + set(${ESV_VARIABLE_PREFIX}_VERSION_STRING "${${ESV_VARIABLE_PREFIX}_VERSION}" PARENT_SCOPE) + +endfunction() diff --git a/client/qtkeychain/cmake/Modules/GNUInstallDirs.cmake b/client/qtkeychain/cmake/Modules/GNUInstallDirs.cmake new file mode 100644 index 0000000..0302e4b --- /dev/null +++ b/client/qtkeychain/cmake/Modules/GNUInstallDirs.cmake @@ -0,0 +1,188 @@ +# - Define GNU standard installation directories +# Provides install directory variables as defined for GNU software: +# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html +# Inclusion of this module defines the following variables: +# CMAKE_INSTALL_ - destination for files of a given type +# CMAKE_INSTALL_FULL_ - corresponding absolute path +# where is one of: +# BINDIR - user executables (bin) +# SBINDIR - system admin executables (sbin) +# LIBEXECDIR - program executables (libexec) +# SYSCONFDIR - read-only single-machine data (etc) +# SHAREDSTATEDIR - modifiable architecture-independent data (com) +# LOCALSTATEDIR - modifiable single-machine data (var) +# LIBDIR - object code libraries (lib or lib64 or lib/ on Debian) +# INCLUDEDIR - C header files (include) +# OLDINCLUDEDIR - C header files for non-gcc (/usr/include) +# DATAROOTDIR - read-only architecture-independent data root (share) +# DATADIR - read-only architecture-independent data (DATAROOTDIR) +# INFODIR - info documentation (DATAROOTDIR/info) +# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) +# MANDIR - man documentation (DATAROOTDIR/man) +# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) +# Each CMAKE_INSTALL_ value may be passed to the DESTINATION options of +# install() commands for the corresponding file type. If the includer does +# not define a value the above-shown default will be used and the value will +# appear in the cache for editing by the user. +# Each CMAKE_INSTALL_FULL_ value contains an absolute path constructed +# from the corresponding destination by prepending (if necessary) the value +# of CMAKE_INSTALL_PREFIX. + +#============================================================================= +# Copyright 2011 Nikita Krupen'ko +# Copyright 2011 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +# Installation directories +# +if(NOT DEFINED CMAKE_INSTALL_BINDIR) + set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SBINDIR) + set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) + set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) + set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) + set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) + set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LIBDIR) + set(_LIBDIR_DEFAULT "lib") + # Override this default 'lib' with 'lib64' iff: + # - we are on Linux system but NOT cross-compiling + # - we are NOT on debian + # - we are on a 64 bits system + # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf + # For Debian with multiarch, use 'lib/${CMAKE_LIBRARY_ARCHITECTURE}' if + # CMAKE_LIBRARY_ARCHITECTURE is set (which contains e.g. "i386-linux-gnu" + # See http://wiki.debian.org/Multiarch + if(CMAKE_SYSTEM_NAME MATCHES "Linux" + AND NOT CMAKE_CROSSCOMPILING) + if (EXISTS "/etc/debian_version") # is this a debian system ? + if(CMAKE_LIBRARY_ARCHITECTURE) + set(_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}") + endif() + else() # not debian, rely on CMAKE_SIZEOF_VOID_P: + if(NOT DEFINED CMAKE_SIZEOF_VOID_P) + message(AUTHOR_WARNING + "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " + "Please enable at least one language before including GNUInstallDirs.") + else() + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(_LIBDIR_DEFAULT "lib64") + endif() + endif() + endif() + endif() + set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") +endif() + +if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) + set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) + set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) + set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") +endif() + +#----------------------------------------------------------------------------- +# Values whose defaults are relative to DATAROOTDIR. Store empty values in +# the cache and store the defaults in local variables if the cache values are +# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. + +if(NOT CMAKE_INSTALL_DATADIR) + set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") + set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") +endif() + +if(NOT CMAKE_INSTALL_INFODIR) + set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") + set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") +endif() + +if(NOT CMAKE_INSTALL_LOCALEDIR) + set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") + set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") +endif() + +if(NOT CMAKE_INSTALL_MANDIR) + set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") + set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") +endif() + +if(NOT CMAKE_INSTALL_DOCDIR) + set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") + set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") +endif() + +#----------------------------------------------------------------------------- + +mark_as_advanced( + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_SBINDIR + CMAKE_INSTALL_LIBEXECDIR + CMAKE_INSTALL_SYSCONFDIR + CMAKE_INSTALL_SHAREDSTATEDIR + CMAKE_INSTALL_LOCALSTATEDIR + CMAKE_INSTALL_LIBDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_OLDINCLUDEDIR + CMAKE_INSTALL_DATAROOTDIR + CMAKE_INSTALL_DATADIR + CMAKE_INSTALL_INFODIR + CMAKE_INSTALL_LOCALEDIR + CMAKE_INSTALL_MANDIR + CMAKE_INSTALL_DOCDIR + ) + +# Result directories +# +foreach(dir + BINDIR + SBINDIR + LIBEXECDIR + SYSCONFDIR + SHAREDSTATEDIR + LOCALSTATEDIR + LIBDIR + INCLUDEDIR + OLDINCLUDEDIR + DATAROOTDIR + DATADIR + INFODIR + LOCALEDIR + MANDIR + DOCDIR + ) + if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) + set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") + else() + set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") + endif() +endforeach() diff --git a/client/qtkeychain/gnomekeyring.cpp b/client/qtkeychain/gnomekeyring.cpp new file mode 100644 index 0000000..dd35670 --- /dev/null +++ b/client/qtkeychain/gnomekeyring.cpp @@ -0,0 +1,86 @@ +#include "gnomekeyring_p.h" + +const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = NULL; + +bool GnomeKeyring::isAvailable() +{ + const GnomeKeyring& keyring = instance(); + return keyring.isLoaded() && + keyring.NETWORK_PASSWORD && + keyring.is_available && + keyring.find_password && + keyring.store_password && + keyring.delete_password && + keyring.is_available(); +} + +GnomeKeyring::gpointer GnomeKeyring::store_network_password( + const gchar* keyring, + const gchar* display_name, + const gchar* user, + const gchar* server, + const gchar* type, + const gchar* password, + OperationDoneCallback callback, + gpointer data, + GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + return instance().store_password( instance().NETWORK_PASSWORD, + keyring, display_name, password, callback, + data, destroy_data, + "user", user, + "server", server, + "type", type, + static_cast(0) ); +} + +GnomeKeyring::gpointer GnomeKeyring::find_network_password( + const gchar* user, const gchar* server, const gchar* type, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + + return instance().find_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, "type", type, + static_cast(0) ); +} + +GnomeKeyring::gpointer GnomeKeyring::delete_network_password( const gchar* user, + const gchar* server, + OperationDoneCallback callback, + gpointer data, + GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + return instance().delete_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, static_cast(0) ); +} + +GnomeKeyring::GnomeKeyring() + : QLibrary(QLatin1String("gnome-keyring"), 0) +{ + static const PasswordSchema schema = { + ITEM_NETWORK_PASSWORD, + {{ "user", ATTRIBUTE_TYPE_STRING }, + { "server", ATTRIBUTE_TYPE_STRING }, + { "type", ATTRIBUTE_TYPE_STRING }, + { 0, static_cast( 0 ) }} + }; + + NETWORK_PASSWORD = &schema; + is_available = reinterpret_cast( resolve( "gnome_keyring_is_available" ) ); + find_password = reinterpret_cast( resolve( "gnome_keyring_find_password" ) ); + store_password = reinterpret_cast( resolve( "gnome_keyring_store_password" ) ); + delete_password = reinterpret_cast( resolve( "gnome_keyring_delete_password" ) ); +} + +GnomeKeyring& GnomeKeyring::instance() { + static GnomeKeyring keyring; + return keyring; +} diff --git a/client/qtkeychain/gnomekeyring_p.h b/client/qtkeychain/gnomekeyring_p.h new file mode 100644 index 0000000..87c062c --- /dev/null +++ b/client/qtkeychain/gnomekeyring_p.h @@ -0,0 +1,94 @@ +#ifndef QTKEYCHAIN_GNOME_P_H +#define QTKEYCHAIN_GNOME_P_H + +#include + +class GnomeKeyring : private QLibrary { + Q_OBJECT + +public: + enum Result { + RESULT_OK, + RESULT_DENIED, + RESULT_NO_KEYRING_DAEMON, + RESULT_ALREADY_UNLOCKED, + RESULT_NO_SUCH_KEYRING, + RESULT_BAD_ARGUMENTS, + RESULT_IO_ERROR, + RESULT_CANCELLED, + RESULT_KEYRING_ALREADY_EXISTS, + RESULT_NO_MATCH + }; + + enum ItemType { + ITEM_GENERIC_SECRET = 0, + ITEM_NETWORK_PASSWORD, + ITEM_NOTE, + ITEM_CHAINED_KEYRING_PASSWORD, + ITEM_ENCRYPTION_KEY_PASSWORD, + ITEM_PK_STORAGE = 0x100 + }; + + enum AttributeType { + ATTRIBUTE_TYPE_STRING, + ATTRIBUTE_TYPE_UINT32 + }; + + typedef char gchar; + typedef void* gpointer; + typedef bool gboolean; + typedef struct { + ItemType item_type; + struct { + const gchar* name; + AttributeType type; + } attributes[32]; + } PasswordSchema; + + typedef void ( *OperationGetStringCallback )( Result result, bool binary, + const char* string, gpointer data ); + typedef void ( *OperationDoneCallback )( Result result, gpointer data ); + typedef void ( *GDestroyNotify )( gpointer data ); + + static const char* GNOME_KEYRING_DEFAULT; + + static bool isAvailable(); + + static gpointer store_network_password( const gchar* keyring, const gchar* display_name, + const gchar* user, const gchar* server, + const gchar* type, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ); + + static gpointer find_network_password( const gchar* user, const gchar* server, + const gchar* type, + OperationGetStringCallback callback, + gpointer data, GDestroyNotify destroy_data ); + + static gpointer delete_network_password( const gchar* user, const gchar* server, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ); +private: + GnomeKeyring(); + + static GnomeKeyring& instance(); + + const PasswordSchema* NETWORK_PASSWORD; + typedef gboolean ( is_available_fn )( void ); + typedef gpointer ( store_password_fn )( const PasswordSchema* schema, const gchar* keyring, + const gchar* display_name, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( find_password_fn )( const PasswordSchema* schema, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( delete_password_fn )( const PasswordSchema* schema, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + + is_available_fn* is_available; + find_password_fn* find_password; + store_password_fn* store_password; + delete_password_fn* delete_password; +}; + + +#endif diff --git a/client/qtkeychain/keychain.cpp b/client/qtkeychain/keychain.cpp new file mode 100644 index 0000000..90ee4eb --- /dev/null +++ b/client/qtkeychain/keychain.cpp @@ -0,0 +1,235 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain.h" +#include "keychain_p.h" + +using namespace QKeychain; + +Job::Job( JobPrivate *q, QObject *parent ) + : QObject( parent ) + , d ( q ) { +} + +Job::~Job() { + delete d; +} + +QString Job::service() const { + return d->service; +} + +QSettings* Job::settings() const { + return d->settings; +} + +void Job::setSettings( QSettings* settings ) { + d->settings = settings; +} + +void Job::start() { + QMetaObject::invokeMethod( this, "doStart", Qt::QueuedConnection ); +} + +bool Job::autoDelete() const { + return d->autoDelete; +} + +void Job::setAutoDelete( bool autoDelete ) { + d->autoDelete = autoDelete; +} + +bool Job::insecureFallback() const { + return d->insecureFallback; +} + +void Job::setInsecureFallback( bool insecureFallback ) { + d->insecureFallback = insecureFallback; +} + +void Job::doStart() { + JobExecutor::instance()->enqueue( this ); +} + +void Job::emitFinished() { + emit finished( this ); + if ( d->autoDelete ) + deleteLater(); +} + +void Job::emitFinishedWithError( Error error, const QString& errorString ) { + d->error = error; + d->errorString = errorString; + emitFinished(); +} + +void Job::scheduledStart() { + d->scheduledStart(); +} + +Error Job::error() const { + return d->error; +} + +QString Job::errorString() const { + return d->errorString; +} + +void Job::setError( Error error ) { + d->error = error; +} + +void Job::setErrorString( const QString& errorString ) { + d->errorString = errorString; +} + +ReadPasswordJob::ReadPasswordJob( const QString& service, QObject* parent ) + : Job( new ReadPasswordJobPrivate( service, this ), parent ) { + +} + +ReadPasswordJob::~ReadPasswordJob() { +} + +QString ReadPasswordJob::textData() const { + return QString::fromUtf8( d->data ); +} + +QByteArray ReadPasswordJob::binaryData() const { + return d->data; +} + +QString Job::key() const { + return d->key; +} + +void Job::setKey( const QString& key_ ) { + d->key = key_; +} + +WritePasswordJob::WritePasswordJob( const QString& service, QObject* parent ) + : Job( new WritePasswordJobPrivate( service, this ), parent ) { +} + +WritePasswordJob::~WritePasswordJob() { +} + +void WritePasswordJob::setBinaryData( const QByteArray& data ) { + d->data = data; + d->mode = JobPrivate::Binary; +} + +void WritePasswordJob::setTextData( const QString& data ) { + d->data = data.toUtf8(); + d->mode = JobPrivate::Text; +} + +DeletePasswordJob::DeletePasswordJob( const QString& service, QObject* parent ) + : Job( new DeletePasswordJobPrivate( service, this ), parent ) { +} + +DeletePasswordJob::~DeletePasswordJob() { +} + +DeletePasswordJobPrivate::DeletePasswordJobPrivate(const QString &service_, DeletePasswordJob *qq) : + JobPrivate(service_, qq) { + +} + +JobExecutor::JobExecutor() + : QObject( 0 ) + , m_jobRunning( false ) { +} + +void JobExecutor::enqueue( Job* job ) { + m_queue.enqueue( job ); + startNextIfNoneRunning(); +} + +void JobExecutor::startNextIfNoneRunning() { + if ( m_queue.isEmpty() || m_jobRunning ) + return; + QPointer next; + while ( !next && !m_queue.isEmpty() ) { + next = m_queue.dequeue(); + } + if ( next ) { + connect( next, SIGNAL(finished(QKeychain::Job*)), this, SLOT(jobFinished(QKeychain::Job*)) ); + connect( next, SIGNAL(destroyed(QObject*)), this, SLOT(jobDestroyed(QObject*)) ); + m_jobRunning = true; + next->scheduledStart(); + } +} + +void JobExecutor::jobDestroyed( QObject* object ) { + Job* job = static_cast(object); + Q_UNUSED( object ) // for release mode + job->disconnect( this ); + m_jobRunning = false; + startNextIfNoneRunning(); +} + +void JobExecutor::jobFinished( Job* job ) { + Q_UNUSED( job ) // for release mode + job->disconnect( this ); + m_jobRunning = false; + startNextIfNoneRunning(); +} + +JobExecutor* JobExecutor::s_instance = 0; + +JobExecutor* JobExecutor::instance() { + if ( !s_instance ) + s_instance = new JobExecutor; + return s_instance; +} + +ReadPasswordJobPrivate::ReadPasswordJobPrivate(const QString &service_, ReadPasswordJob *qq) : + JobPrivate(service_, qq) { + +} + +JobPrivate::JobPrivate(const QString &service_, Job *qq) + : q(qq) + , mode( Text ) + , error( NoError ) + , service( service_ ) + , autoDelete( true ) + , insecureFallback( false ) +{ +} + +QString JobPrivate::modeToString(Mode m) +{ + switch (m) { + case Text: + return QLatin1String("Text"); + case Binary: + return QLatin1String("Binary"); + } + + Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled Mode value"); + return QString(); +} + +JobPrivate::Mode JobPrivate::stringToMode(const QString& s) +{ + if (s == QLatin1String("Text") || s == QLatin1String("1")) + return Text; + if (s == QLatin1String("Binary") || s == QLatin1String("2")) + return Binary; + + qCritical("Unexpected mode string '%s'", qPrintable(s)); + + return Text; +} + +WritePasswordJobPrivate::WritePasswordJobPrivate(const QString &service_, WritePasswordJob *qq) : + JobPrivate(service_, qq) { + +} diff --git a/client/qtkeychain/keychain.h b/client/qtkeychain/keychain.h new file mode 100644 index 0000000..1d7a5fd --- /dev/null +++ b/client/qtkeychain/keychain.h @@ -0,0 +1,271 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#ifndef KEYCHAIN_H +#define KEYCHAIN_H + +#if !defined(QTKEYCHAIN_NO_EXPORT) +#include "qkeychain_export.h" +#else +#define QKEYCHAIN_EXPORT +#endif + +#include +#include + +class QSettings; + +#define QTKEYCHAIN_VERSION 0x000100 + +namespace QKeychain { + +/** + * Error codes + */ +enum Error { + NoError=0, /**< No error occurred, operation was successful */ + EntryNotFound, /**< For the given key no data was found */ + CouldNotDeleteEntry, /**< Could not delete existing secret data */ + AccessDeniedByUser, /**< User denied access to keychain */ + AccessDenied, /**< Access denied for other reasons */ + NoBackendAvailable, /**< No platform-specific keychain service available */ + NotImplemented, /**< Not implemented on platform */ + OtherError /**< Something else went wrong (errorString() might provide details) */ +}; + +class JobExecutor; +class JobPrivate; + +/** + * @brief Abstract base class for all QKeychain jobs. + */ +class QKEYCHAIN_EXPORT Job : public QObject { + Q_OBJECT +public: + ~Job(); + + /** + * @return The QSettings instance used as plaintext storage if insecureFallback() is true. + * @see setSettings() + * @see insecureFallback() + */ + QSettings* settings() const; + + /** + * @return Set the QSettings instance that will be used as plaintext storage if insecureFallback() is true. + * @see settings() + * @see insecureFallback() + */ + void setSettings( QSettings* settings ); + + /** + * Call this method to start the job. + * Typically you want to connect some slot to the finished() signal first: + * + * \code + * SomeClass::startJob() + * { + * connect(job, &Job::finished, this, &SomeClass::slotJobFinished); + * job->start(); + * } + * + * SomeClass::slotJobFinished(Job *job) + * { + * if (job->error()) { + * // handle error + * } else { + * // do job-specific stuff + * } + * } + * \endcode + * + * @see finished() + */ + void start(); + + QString service() const; + + /** + * @note Call this method only after finished() has been emitted. + * @return The error code of the job (0 if no error). + */ + Error error() const; + + /** + * @return An error message that might provide details if error() returns OtherError. + */ + QString errorString() const; + + /** + * @return Whether this job autodeletes itself once finished() has been emitted. Default is true. + * @see setAutoDelete() + */ + bool autoDelete() const; + + /** + * Set whether this job should autodelete itself once finished() has been emitted. + * @see autoDelete() + */ + void setAutoDelete( bool autoDelete ); + + /** + * @return Whether this job will use plaintext storage on unsupported platforms. Default is false. + * @see setInsecureFallback() + */ + bool insecureFallback() const; + + /** + * Set whether this job should use plaintext storage on unsupported platforms. + * @see insecureFallback() + */ + void setInsecureFallback( bool insecureFallback ); + + /** + * @return The string used as key by this job. + * @see setKey() + */ + QString key() const; + + /** + * Set the @p key that this job will use to read or write data from/to the keychain. + * The key can be an empty string. + * @see key() + */ + void setKey( const QString& key ); + + void emitFinished(); + void emitFinishedWithError(Error, const QString& errorString); + +Q_SIGNALS: + /** + * Emitted when this job is finished. + * You can connect to this signal to be notified about the job's completion. + * @see start() + */ + void finished( QKeychain::Job* ); + +protected: + explicit Job( JobPrivate *q, QObject* parent=0 ); + Q_INVOKABLE void doStart(); + +private: + void setError( Error error ); + void setErrorString( const QString& errorString ); + + void scheduledStart(); + +protected: + JobPrivate* const d; + +friend class JobExecutor; +friend class JobPrivate; +friend class ReadPasswordJobPrivate; +friend class WritePasswordJobPrivate; +friend class DeletePasswordJobPrivate; +}; + +class ReadPasswordJobPrivate; + +/** + * @brief Job for reading secrets from the keychain. + * You can use a ReadPasswordJob to read passwords or binary data from the keychain. + * This job requires a "service" string, which is basically a namespace of keys within the keychain. + * This means that you can read all the pairs stored in the same service string. + */ +class QKEYCHAIN_EXPORT ReadPasswordJob : public Job { + Q_OBJECT +public: + /** + * Create a new ReadPasswordJob. + * @param service The service string used by this job (can be empty). + * @param parent The parent of this job. + */ + explicit ReadPasswordJob( const QString& service, QObject* parent=0 ); + ~ReadPasswordJob(); + + /** + * @return The binary data stored as value of this job's key(). + * @see Job::key() + */ + QByteArray binaryData() const; + + /** + * @return The string stored as value of this job's key(). + * @see Job::key() + * @warning Returns meaningless data if the data was stored as binary data. + * @see WritePasswordJob::setTextData() + */ + QString textData() const; + +private: + friend class QKeychain::ReadPasswordJobPrivate; +}; + +class WritePasswordJobPrivate; + +/** + * @brief Job for writing secrets to the keychain. + * You can use a WritePasswordJob to store passwords or binary data in the keychain. + * This job requires a "service" string, which is basically a namespace of keys within the keychain. + * This means that you can store different pairs under the same service string. + */ +class QKEYCHAIN_EXPORT WritePasswordJob : public Job { + Q_OBJECT +public: + /** + * Create a new WritePasswordJob. + * @param service The service string used by this job (can be empty). + * @param parent The parent of this job. + */ + explicit WritePasswordJob( const QString& service, QObject* parent=0 ); + ~WritePasswordJob(); + + /** + * Set the @p data that the job will store in the keychain as binary data. + * @warning setBinaryData() and setTextData() are mutually exclusive. + */ + void setBinaryData( const QByteArray& data ); + + /** + * Set the @p data that the job will store in the keychain as string. + * Typically @p data is a password. + * @warning setBinaryData() and setTextData() are mutually exclusive. + */ + void setTextData( const QString& data ); + +private: + + friend class QKeychain::WritePasswordJobPrivate; +}; + +class DeletePasswordJobPrivate; + +/** + * @brief Job for deleting secrets from the keychain. + * You can use a DeletePasswordJob to delete passwords or binary data from the keychain. + * This job requires a "service" string, which is basically a namespace of keys within the keychain. + * This means that you can delete all the pairs stored in the same service string. + */ +class QKEYCHAIN_EXPORT DeletePasswordJob : public Job { + Q_OBJECT +public: + /** + * Create a new DeletePasswordJob. + * @param service The service string used by this job (can be empty). + * @param parent The parent of this job. + */ + explicit DeletePasswordJob( const QString& service, QObject* parent=0 ); + ~DeletePasswordJob(); + +private: + friend class QKeychain::DeletePasswordJobPrivate; +}; + +} // namespace QtKeychain + +#endif diff --git a/client/qtkeychain/keychain_ios.mm b/client/qtkeychain/keychain_ios.mm new file mode 100644 index 0000000..4662e40 --- /dev/null +++ b/client/qtkeychain/keychain_ios.mm @@ -0,0 +1,146 @@ +/****************************************************************************** + * Copyright (C) 2016 Mathias Hasselmann * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ + +#include "keychain_p.h" + +#import +#import + +using namespace QKeychain; + +struct ErrorDescription +{ + QKeychain::Error code; + QString message; + + ErrorDescription(QKeychain::Error code, const QString &message) + : code(code), message(message) {} + + static ErrorDescription fromStatus(OSStatus status) + { + switch(status) { + case errSecSuccess: + return ErrorDescription(QKeychain::NoError, Job::tr("No error")); + case errSecItemNotFound: + return ErrorDescription(QKeychain::EntryNotFound, Job::tr("The specified item could not be found in the keychain")); + case errSecUserCanceled: + return ErrorDescription(QKeychain::AccessDeniedByUser, Job::tr("User canceled the operation")); + case errSecInteractionNotAllowed: + return ErrorDescription(QKeychain::AccessDenied, Job::tr("User interaction is not allowed")); + case errSecNotAvailable: + return ErrorDescription(QKeychain::AccessDenied, Job::tr("No keychain is available. You may need to restart your computer")); + case errSecAuthFailed: + return ErrorDescription(QKeychain::AccessDenied, Job::tr("The user name or passphrase you entered is not correct")); + case errSecVerifyFailed: + return ErrorDescription(QKeychain::AccessDenied, Job::tr("A cryptographic verification failure has occurred")); + case errSecUnimplemented: + return ErrorDescription(QKeychain::NotImplemented, Job::tr("Function or operation not implemented")); + case errSecIO: + return ErrorDescription(QKeychain::OtherError, Job::tr("I/O error")); + case errSecOpWr: + return ErrorDescription(QKeychain::OtherError, Job::tr("Already open with with write permission")); + case errSecParam: + return ErrorDescription(QKeychain::OtherError, Job::tr("Invalid parameters passed to a function")); + case errSecAllocate: + return ErrorDescription(QKeychain::OtherError, Job::tr("Failed to allocate memory")); + case errSecBadReq: + return ErrorDescription(QKeychain::OtherError, Job::tr("Bad parameter or invalid state for operation")); + case errSecInternalComponent: + return ErrorDescription(QKeychain::OtherError, Job::tr("An internal component failed")); + case errSecDuplicateItem: + return ErrorDescription(QKeychain::OtherError, Job::tr("The specified item already exists in the keychain")); + case errSecDecode: + return ErrorDescription(QKeychain::OtherError, Job::tr("Unable to decode the provided data")); + } + + return ErrorDescription(QKeychain::OtherError, Job::tr("Unknown error")); + } +}; + +void ReadPasswordJobPrivate::scheduledStart() +{ + NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + (__bridge id) kSecReturnData: @YES, + }; + + CFTypeRef dataRef = nil; + const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef); + + data.clear(); + mode = Binary; + + if (status == errSecSuccess) { + if (dataRef) + data = QByteArray::fromCFData((CFDataRef) dataRef); + + q->emitFinished(); + } else { + const ErrorDescription error = ErrorDescription::fromStatus(status); + q->emitFinishedWithError(error.code, Job::tr("Could not retrieve private key from keystore: %1").arg(error.message)); + } + + if (dataRef) + [dataRef release]; +} + +void WritePasswordJobPrivate::scheduledStart() +{ + NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + }; + + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, nil); + + if (status == errSecSuccess) { + NSDictionary *const update = @{ + (__bridge id) kSecValueData: (__bridge NSData *) data.toCFData(), + }; + + status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update); + } else { + NSDictionary *const insert = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + (__bridge id) kSecValueData: (__bridge NSData *) data.toCFData(), + }; + + status = SecItemAdd((__bridge CFDictionaryRef) insert, nil); + } + + if (status == errSecSuccess) { + q->emitFinished(); + } else { + const ErrorDescription error = ErrorDescription::fromStatus(status); + q->emitFinishedWithError(error.code, tr("Could not store data in settings: %1").arg(error.message)); + } +} + +void DeletePasswordJobPrivate::scheduledStart() +{ + const NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + }; + + const OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); + + if (status == errSecSuccess) { + q->emitFinished(); + } else { + const ErrorDescription error = ErrorDescription::fromStatus(status); + q->emitFinishedWithError(error.code, Job::tr("Could not remove private key from keystore: %1").arg(error.message)); + } +} diff --git a/client/qtkeychain/keychain_mac.cpp b/client/qtkeychain/keychain_mac.cpp new file mode 100644 index 0000000..dc8365b --- /dev/null +++ b/client/qtkeychain/keychain_mac.cpp @@ -0,0 +1,163 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" + +#include +#include +#include + +using namespace QKeychain; + +template +struct Releaser { + explicit Releaser( const T& v ) : value( v ) {} + ~Releaser() { + CFRelease( value ); + } + + const T value; +}; + +static QString strForStatus( OSStatus os ) { + const Releaser str( SecCopyErrorMessageString( os, 0 ) ); + const char * const buf = CFStringGetCStringPtr( str.value, kCFStringEncodingUTF8 ); + if ( !buf ) + return QObject::tr( "OS X Keychain error (OSStatus %1)" ).arg( os ); + return QObject::tr( "%1 (OSStatus %2)" ) + .arg( QString::fromUtf8( buf, strlen( buf ) ) ).arg( os ); +} + +static OSStatus readPw( QByteArray* pw, + const QString& service, + const QString& account, + SecKeychainItemRef* ref ) { + Q_ASSERT( pw ); + pw->clear(); + const QByteArray serviceData = service.toUtf8(); + const QByteArray accountData = account.toUtf8(); + + void* data = 0; + UInt32 len = 0; + + const OSStatus ret = SecKeychainFindGenericPassword( NULL, // default keychain + serviceData.size(), + serviceData.constData(), + accountData.size(), + accountData.constData(), + &len, + &data, + ref ); + if ( ret == noErr ) { + *pw = QByteArray( reinterpret_cast( data ), len ); + const OSStatus ret2 = SecKeychainItemFreeContent ( 0, data ); + if ( ret2 != noErr ) + qWarning() << "Could not free item content: " << strForStatus( ret2 ); + } + return ret; +} + +void ReadPasswordJobPrivate::scheduledStart() +{ + QString errorString; + Error error = NoError; + const OSStatus ret = readPw( &data, q->service(), q->key(), 0 ); + + switch ( ret ) { + case noErr: + break; + case errSecItemNotFound: + errorString = tr("Password not found"); + error = EntryNotFound; + break; + default: + errorString = strForStatus( ret ); + error = OtherError; + break; + } + q->emitFinishedWithError( error, errorString ); +} + + +static QKeychain::Error deleteEntryImpl( const QString& service, const QString& account, QString* err ) { + SecKeychainItemRef ref; + QByteArray pw; + const OSStatus ret1 = readPw( &pw, service, account, &ref ); + if ( ret1 == errSecItemNotFound ) + return NoError; // No item stored, we're done + if ( ret1 != noErr ) { + *err = strForStatus( ret1 ); + //TODO map error code, set errstr + return OtherError; + } + const Releaser releaser( ref ); + + const OSStatus ret2 = SecKeychainItemDelete( ref ); + + if ( ret2 == noErr ) + return NoError; + //TODO map error code + *err = strForStatus( ret2 ); + return CouldNotDeleteEntry; +} + +static QKeychain::Error writeEntryImpl( const QString& service, + const QString& account, + const QByteArray& data, + QString* err ) { + Q_ASSERT( err ); + err->clear(); + const QByteArray serviceData = service.toUtf8(); + const QByteArray accountData = account.toUtf8(); + const OSStatus ret = SecKeychainAddGenericPassword( NULL, //default keychain + serviceData.size(), + serviceData.constData(), + accountData.size(), + accountData.constData(), + data.size(), + data.constData(), + NULL //item reference + ); + if ( ret != noErr ) { + switch ( ret ) { + case errSecDuplicateItem: + { + Error derr = deleteEntryImpl( service, account, err ); + if ( derr != NoError ) + return CouldNotDeleteEntry; + else + return writeEntryImpl( service, account, data, err ); + } + default: + *err = strForStatus( ret ); + return OtherError; + } + } + + return NoError; +} + +void WritePasswordJobPrivate::scheduledStart() +{ + QString errorString; + Error error = NoError; + + error = writeEntryImpl( q->service(), key, data, &errorString ); + q->emitFinishedWithError( error, errorString ); +} + +void DeletePasswordJobPrivate::scheduledStart() +{ + QString errorString; + Error error = NoError; + + const Error derr = deleteEntryImpl( q->service(), key, &errorString ); + if ( derr != NoError ) + error = CouldNotDeleteEntry; + q->emitFinishedWithError( error, errorString ); +} diff --git a/client/qtkeychain/keychain_p.h b/client/qtkeychain/keychain_p.h new file mode 100644 index 0000000..ab7f0be --- /dev/null +++ b/client/qtkeychain/keychain_p.h @@ -0,0 +1,167 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#ifndef KEYCHAIN_P_H +#define KEYCHAIN_P_H + +#include +#include +#include +#include +#include + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) + +#include + +#include "kwallet_interface.h" +#else + +class QDBusPendingCallWatcher; + +#endif + +#include "keychain.h" + +namespace QKeychain { + +class JobExecutor; + +class JobPrivate : public QObject { + Q_OBJECT +public: + enum Mode { + Text, + Binary + }; + + virtual void scheduledStart() = 0; + + static QString modeToString(Mode m); + static Mode stringToMode(const QString& s); + + Job* const q; + Mode mode; + QByteArray data; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) + org::kde::KWallet* iface; + int walletHandle; + + static void gnomeKeyring_readCb( int result, const char* string, JobPrivate* data ); + static void gnomeKeyring_writeCb( int result, JobPrivate* self ); + + virtual void fallbackOnError(const QDBusError& err) = 0; + +protected Q_SLOTS: + void kwalletWalletFound( QDBusPendingCallWatcher* watcher ); + virtual void kwalletFinished( QDBusPendingCallWatcher* watcher ); + virtual void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ); +#else + void kwalletWalletFound( QDBusPendingCallWatcher* ) {} + virtual void kwalletFinished( QDBusPendingCallWatcher* ) {} + virtual void kwalletOpenFinished( QDBusPendingCallWatcher* ) {} +#endif + +protected: + JobPrivate( const QString& service_, Job *q ); + +protected: + QKeychain::Error error; + QString errorString; + QString service; + bool autoDelete; + bool insecureFallback; + QPointer settings; + QString key; + +friend class Job; +friend class JobExecutor; +friend class ReadPasswordJob; +friend class WritePasswordJob; +friend class PlainTextStore; +}; + +class ReadPasswordJobPrivate : public JobPrivate { + Q_OBJECT +public: + explicit ReadPasswordJobPrivate( const QString &service_, ReadPasswordJob* qq ); + void scheduledStart(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) + void fallbackOnError(const QDBusError& err); + +private Q_SLOTS: + void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ); + void kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher ); + void kwalletFinished( QDBusPendingCallWatcher* watcher ); +#else //moc's too dumb to respect above macros, so just define empty slot implementations +private Q_SLOTS: + void kwalletOpenFinished( QDBusPendingCallWatcher* ) {} + void kwalletEntryTypeFinished( QDBusPendingCallWatcher* ) {} + void kwalletFinished( QDBusPendingCallWatcher* ) {} +#endif + + friend class ReadPasswordJob; +}; + +class WritePasswordJobPrivate : public JobPrivate { + Q_OBJECT +public: + explicit WritePasswordJobPrivate( const QString &service_, WritePasswordJob* qq ); + void scheduledStart(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) + void fallbackOnError(const QDBusError& err); +#endif + + friend class WritePasswordJob; +}; + +class DeletePasswordJobPrivate : public JobPrivate { + Q_OBJECT +public: + explicit DeletePasswordJobPrivate( const QString &service_, DeletePasswordJob* qq ); + + void scheduledStart(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) + void fallbackOnError(const QDBusError& err); +#endif + +protected: + void doStart(); + + friend class DeletePasswordJob; +}; + +class JobExecutor : public QObject { + Q_OBJECT +public: + + static JobExecutor* instance(); + + void enqueue( Job* job ); + +private: + explicit JobExecutor(); + void startNextIfNoneRunning(); + +private Q_SLOTS: + void jobFinished( QKeychain::Job* ); + void jobDestroyed( QObject* object ); + +private: + static JobExecutor* s_instance; + QQueue > m_queue; + bool m_jobRunning; +}; + +} + +#endif // KEYCHAIN_P_H diff --git a/client/qtkeychain/keychain_unix.cpp b/client/qtkeychain/keychain_unix.cpp new file mode 100644 index 0000000..958927a --- /dev/null +++ b/client/qtkeychain/keychain_unix.cpp @@ -0,0 +1,586 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" +#include "gnomekeyring_p.h" +#include "libsecret_p.h" +#include "plaintextstore_p.h" + +#include + +using namespace QKeychain; + +enum KeyringBackend { + Backend_LibSecretKeyring, + Backend_GnomeKeyring, + Backend_Kwallet4, + Backend_Kwallet5 +}; + +enum DesktopEnvironment { + DesktopEnv_Gnome, + DesktopEnv_Kde4, + DesktopEnv_Plasma5, + DesktopEnv_Unity, + DesktopEnv_Xfce, + DesktopEnv_Other +}; + +// the following detection algorithm is derived from chromium, +// licensed under BSD, see base/nix/xdg_util.cc + +static DesktopEnvironment getKdeVersion() { + QByteArray value = qgetenv("KDE_SESSION_VERSION"); + if ( value == "5" ) { + return DesktopEnv_Plasma5; + } else if (value == "4" ) { + return DesktopEnv_Kde4; + } else { + // most likely KDE3 + return DesktopEnv_Other; + } +} + +static DesktopEnvironment detectDesktopEnvironment() { + QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); + if ( xdgCurrentDesktop == "GNOME" ) { + return DesktopEnv_Gnome; + } else if ( xdgCurrentDesktop == "Unity" ) { + return DesktopEnv_Unity; + } else if ( xdgCurrentDesktop == "KDE" ) { + return getKdeVersion(); + } else if ( xdgCurrentDesktop == "XFCE" ) { + return DesktopEnv_Xfce; + } + + QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); + if ( desktopSession == "gnome" ) { + return DesktopEnv_Gnome; + } else if ( desktopSession == "kde" ) { + return getKdeVersion(); + } else if ( desktopSession == "kde4" ) { + return DesktopEnv_Kde4; + } else if ( desktopSession.contains("xfce") || desktopSession == "xubuntu" ) { + return DesktopEnv_Xfce; + } + + if ( !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty() ) { + return DesktopEnv_Gnome; + } else if ( !qgetenv("KDE_FULL_SESSION").isEmpty() ) { + return getKdeVersion(); + } + + return DesktopEnv_Other; +} + +static bool isKwallet5Available() +{ + if (!QDBusConnection::sessionBus().isConnected()) + return false; + + org::kde::KWallet iface( + QLatin1String("org.kde.kwalletd5"), + QLatin1String("/modules/kwalletd5"), + QDBusConnection::sessionBus()); + + // At this point iface.isValid() can return false even though the + // interface is activatable by making a call. Hence we check whether + // a wallet can be opened. + + iface.setTimeout(500); + QDBusMessage reply = iface.call(QStringLiteral("networkWallet")); + return reply.type() == QDBusMessage::ReplyMessage; +} + +static KeyringBackend detectKeyringBackend() +{ + /* The secret service dbus api, accessible through libsecret, is supposed + * to unify password services. + * + * Unfortunately at the time of Kubuntu 18.04 the secret service backend + * in KDE is gnome-keyring-daemon - using it has several complications: + * - the default collection isn't opened on session start, so users need + * to manually unlock it when the first application uses it + * - it's separate from the kwallet5 keyring, so switching to it means the + * existing keyring data can't be accessed anymore + * + * Thus we still prefer kwallet backends on KDE even if libsecret is + * available. + */ + + switch (detectDesktopEnvironment()) { + case DesktopEnv_Kde4: + return Backend_Kwallet4; + + case DesktopEnv_Plasma5: + if (isKwallet5Available()) { + return Backend_Kwallet5; + } + if (LibSecretKeyring::isAvailable()) { + return Backend_LibSecretKeyring; + } + if (GnomeKeyring::isAvailable()) { + return Backend_GnomeKeyring; + } + // During startup the keychain backend might just not have started yet + return Backend_Kwallet5; + + case DesktopEnv_Gnome: + case DesktopEnv_Unity: + case DesktopEnv_Xfce: + case DesktopEnv_Other: + default: + if (LibSecretKeyring::isAvailable()) { + return Backend_LibSecretKeyring; + } + if (GnomeKeyring::isAvailable()) { + return Backend_GnomeKeyring; + } + if (isKwallet5Available()) { + return Backend_Kwallet5; + } + // During startup the keychain backend might just not have started yet + // + // This doesn't need to be libsecret because LibSecretKeyring::isAvailable() + // only fails if the libsecret shared library couldn't be loaded. In contrast + // to that GnomeKeyring::isAvailable() can return false if the shared library + // *was* loaded but its libgnome_keyring::is_available() returned false. + // + // In the future there should be a difference between "API available" and + // "keychain available". + return Backend_GnomeKeyring; + } + +} + +static KeyringBackend getKeyringBackend() +{ + static KeyringBackend backend = detectKeyringBackend(); + return backend; +} + +static void kwalletReadPasswordScheduledStartImpl(const char * service, const char * path, ReadPasswordJobPrivate * priv) { + if ( QDBusConnection::sessionBus().isConnected() ) + { + priv->iface = new org::kde::KWallet( QLatin1String(service), QLatin1String(path), QDBusConnection::sessionBus(), priv ); + const QDBusPendingReply reply = priv->iface->networkWallet(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, priv ); + priv->connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), priv, SLOT(kwalletWalletFound(QDBusPendingCallWatcher*)) ); + } + else + { + // D-Bus is not reachable so none can tell us something about KWalletd + QDBusError err( QDBusError::NoServer, ReadPasswordJobPrivate::tr("D-Bus is not running") ); + priv->fallbackOnError( err ); + } +} + +void ReadPasswordJobPrivate::scheduledStart() { + switch ( getKeyringBackend() ) { + case Backend_LibSecretKeyring: { + if ( !LibSecretKeyring::findPassword(key, q->service(), this) ) { + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + } break; + case Backend_GnomeKeyring: + this->mode = JobPrivate::Text; + if ( !GnomeKeyring::find_network_password( key.toUtf8().constData(), + q->service().toUtf8().constData(), + "plaintext", + reinterpret_cast( &JobPrivate::gnomeKeyring_readCb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + break; + + case Backend_Kwallet4: + kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd", "/modules/kwalletd", this); + break; + case Backend_Kwallet5: + kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd5", "/modules/kwalletd5", this); + break; + } +} + +void JobPrivate::kwalletWalletFound(QDBusPendingCallWatcher *watcher) +{ + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + const QDBusPendingReply pendingReply = iface->open( reply.value(), 0, q->service() ); + QDBusPendingCallWatcher* pendingWatcher = new QDBusPendingCallWatcher( pendingReply, this ); + connect( pendingWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); +} + +static QPair mapGnomeKeyringError( int result ) +{ + Q_ASSERT( result != GnomeKeyring::RESULT_OK ); + + switch ( result ) { + case GnomeKeyring::RESULT_DENIED: + return qMakePair( AccessDenied, QObject::tr("Access to keychain denied") ); + case GnomeKeyring::RESULT_NO_KEYRING_DAEMON: + return qMakePair( NoBackendAvailable, QObject::tr("No keyring daemon") ); + case GnomeKeyring::RESULT_ALREADY_UNLOCKED: + return qMakePair( OtherError, QObject::tr("Already unlocked") ); + case GnomeKeyring::RESULT_NO_SUCH_KEYRING: + return qMakePair( OtherError, QObject::tr("No such keyring") ); + case GnomeKeyring::RESULT_BAD_ARGUMENTS: + return qMakePair( OtherError, QObject::tr("Bad arguments") ); + case GnomeKeyring::RESULT_IO_ERROR: + return qMakePair( OtherError, QObject::tr("I/O error") ); + case GnomeKeyring::RESULT_CANCELLED: + return qMakePair( OtherError, QObject::tr("Cancelled") ); + case GnomeKeyring::RESULT_KEYRING_ALREADY_EXISTS: + return qMakePair( OtherError, QObject::tr("Keyring already exists") ); + case GnomeKeyring::RESULT_NO_MATCH: + return qMakePair( EntryNotFound, QObject::tr("No match") ); + default: + break; + } + + return qMakePair( OtherError, QObject::tr("Unknown error") ); +} + +void JobPrivate::gnomeKeyring_readCb( int result, const char* string, JobPrivate* self ) +{ + if ( result == GnomeKeyring::RESULT_OK ) { + if (self->mode == JobPrivate::Text) + self->data = QByteArray(string); + else + self->data = QByteArray::fromBase64(string); + + self->q->emitFinished(); + } else if (self->mode == JobPrivate::Text) { + self->mode = JobPrivate::Binary; + if ( !GnomeKeyring::find_network_password( self->key.toUtf8().constData(), + self->q->service().toUtf8().constData(), + "base64", + reinterpret_cast( &JobPrivate::gnomeKeyring_readCb ), + self, 0 ) ) + self->q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } else { + const QPair errorResult = mapGnomeKeyringError( result ); + self->q->emitFinishedWithError( errorResult.first, errorResult.second ); + } +} + +void ReadPasswordJobPrivate::fallbackOnError(const QDBusError& err ) +{ + PlainTextStore plainTextStore( q->service(), q->settings() ); + + if ( q->insecureFallback() && plainTextStore.contains( key ) ) { + mode = plainTextStore.readMode( key ); + data = plainTextStore.readData( key ); + + if ( plainTextStore.error() != NoError ) + q->emitFinishedWithError( plainTextStore.error(), plainTextStore.errorString() ); + else + q->emitFinished(); + } else { + if ( err.type() == QDBusError::ServiceUnknown ) //KWalletd not running + q->emitFinishedWithError( NoBackendAvailable, tr("No keychain service available") ); + else + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + } +} + +void ReadPasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + + if ( reply.isError() ) { + fallbackOnError( reply.error() ); + return; + } + + PlainTextStore plainTextStore( q->service(), q->settings() ); + + if ( plainTextStore.contains( key ) ) { + // We previously stored data in the insecure QSettings, but now have KWallet available. + // Do the migration + + data = plainTextStore.readData( key ); + const WritePasswordJobPrivate::Mode mode = plainTextStore.readMode( key ); + plainTextStore.remove( key ); + + q->emitFinished(); + + + WritePasswordJob* j = new WritePasswordJob( q->service(), 0 ); + j->setSettings( q->settings() ); + j->setKey( key ); + j->setAutoDelete( true ); + if ( mode == WritePasswordJobPrivate::Binary ) + j->setBinaryData( data ); + else if ( mode == WritePasswordJobPrivate::Text ) + j->setTextData( QString::fromUtf8( data ) ); + else + Q_ASSERT( false ); + + j->start(); + + return; + } + + walletHandle = reply.value(); + + if ( walletHandle < 0 ) { + q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); + return; + } + + const QDBusPendingReply nextReply = iface->entryType( walletHandle, q->service(), key, q->service() ); + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletEntryTypeFinished(QDBusPendingCallWatcher*)) ); +} + +//Must be in sync with KWallet::EntryType (kwallet.h) +enum KWalletEntryType { + Unknown=0, + Password, + Stream, + Map +}; + +void ReadPasswordJobPrivate::kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + if ( watcher->isError() ) { + const QDBusError err = watcher->error(); + q->emitFinishedWithError( OtherError, tr("Could not determine data type: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + const QDBusPendingReply reply = *watcher; + const int value = reply.value(); + + switch ( value ) { + case Unknown: + q->emitFinishedWithError( EntryNotFound, tr("Entry not found") ); + return; + case Password: + mode = Text; + break; + case Stream: + mode = Binary; + break; + case Map: + q->emitFinishedWithError( EntryNotFound, tr("Unsupported entry type 'Map'") ); + return; + default: + q->emitFinishedWithError( OtherError, tr("Unknown kwallet entry type '%1'").arg( value ) ); + return; + } + + const QDBusPendingCall nextReply = (mode == Text) + ? QDBusPendingCall( iface->readPassword( walletHandle, q->service(), key, q->service() ) ) + : QDBusPendingCall( iface->readEntry( walletHandle, q->service(), key, q->service() ) ); + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletFinished(QDBusPendingCallWatcher*)) ); +} + +void ReadPasswordJobPrivate::kwalletFinished( QDBusPendingCallWatcher* watcher ) { + if ( !watcher->isError() ) { + if ( mode == Binary ) { + QDBusPendingReply reply = *watcher; + if (reply.isValid()) { + data = reply.value(); + } + } else { + QDBusPendingReply reply = *watcher; + if (reply.isValid()) { + data = reply.value().toUtf8(); + } + } + } + + JobPrivate::kwalletFinished(watcher); +} + +static void kwalletWritePasswordScheduledStart( const char * service, const char * path, JobPrivate * priv ) { + if ( QDBusConnection::sessionBus().isConnected() ) + { + priv->iface = new org::kde::KWallet( QLatin1String(service), QLatin1String(path), QDBusConnection::sessionBus(), priv ); + const QDBusPendingReply reply = priv->iface->networkWallet(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, priv ); + priv->connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), priv, SLOT(kwalletWalletFound(QDBusPendingCallWatcher*)) ); + } + else + { + // D-Bus is not reachable so none can tell us something about KWalletd + QDBusError err( QDBusError::NoServer, WritePasswordJobPrivate::tr("D-Bus is not running") ); + priv->fallbackOnError( err ); + } +} + +void WritePasswordJobPrivate::scheduledStart() { + switch ( getKeyringBackend() ) { + case Backend_LibSecretKeyring: { + if ( !LibSecretKeyring::writePassword(service, key, service, mode, + data, this) ) { + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + } break; + case Backend_GnomeKeyring: { + QString type; + QByteArray password; + + switch(mode) { + case JobPrivate::Text: + type = QLatin1String("plaintext"); + password = data; + break; + default: + type = QLatin1String("base64"); + password = data.toBase64(); + break; + } + + QByteArray service = q->service().toUtf8(); + if ( !GnomeKeyring::store_network_password( GnomeKeyring::GNOME_KEYRING_DEFAULT, + service.constData(), + key.toUtf8().constData(), + service.constData(), + type.toUtf8().constData(), + password.constData(), + reinterpret_cast( &JobPrivate::gnomeKeyring_writeCb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + break; + + case Backend_Kwallet4: + kwalletWritePasswordScheduledStart("org.kde.kwalletd", "/modules/kwalletd", this); + break; + case Backend_Kwallet5: + kwalletWritePasswordScheduledStart("org.kde.kwalletd5", "/modules/kwalletd5", this); + break; + } +} + +void WritePasswordJobPrivate::fallbackOnError(const QDBusError &err) +{ + if ( !q->insecureFallback() ) { + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + PlainTextStore plainTextStore( q->service(), q->settings() ); + plainTextStore.write( key, data, mode ); + + if ( plainTextStore.error() != NoError ) + q->emitFinishedWithError( plainTextStore.error(), plainTextStore.errorString() ); + else + q->emitFinished(); +} + +void JobPrivate::gnomeKeyring_writeCb(int result, JobPrivate* self ) +{ + if ( result == GnomeKeyring::RESULT_OK ) { + self->q->emitFinished(); + } else { + const QPair errorResult = mapGnomeKeyringError( result ); + self->q->emitFinishedWithError( errorResult.first, errorResult.second ); + } +} + +void JobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + + if ( reply.isError() ) { + fallbackOnError( reply.error() ); + return; + } + + PlainTextStore plainTextStore( q->service(), q->settings() ); + if ( plainTextStore.contains( key ) ) { + // If we had previously written to QSettings, but we now have a kwallet available, migrate and delete old insecure data + plainTextStore.remove( key ); + } + + const int handle = reply.value(); + + if ( handle < 0 ) { + q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); + return; + } + + QDBusPendingReply nextReply; + + if ( mode == Text ) + nextReply = iface->writePassword( handle, q->service(), key, QString::fromUtf8(data), q->service() ); + else if ( mode == Binary ) + nextReply = iface->writeEntry( handle, q->service(), key, data, q->service() ); + else + nextReply = iface->removeEntry( handle, q->service(), key, q->service() ); + + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletFinished(QDBusPendingCallWatcher*)) ); +} + +void JobPrivate::kwalletFinished( QDBusPendingCallWatcher* watcher ) { + if ( !watcher->isError() ) { + if ( mode == Binary ) { + QDBusPendingReply reply = *watcher; + if (reply.isValid()) { + data = reply.value(); + } + } else { + QDBusPendingReply reply = *watcher; + if (reply.isValid()) { + data = reply.value().toUtf8(); + } + } + } + + q->emitFinished(); +} + +void DeletePasswordJobPrivate::scheduledStart() { + switch ( getKeyringBackend() ) { + case Backend_LibSecretKeyring: { + if ( !LibSecretKeyring::deletePassword(key, q->service(), this) ) { + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + } break; + case Backend_GnomeKeyring: { + if ( !GnomeKeyring::delete_network_password( + key.toUtf8().constData(), q->service().toUtf8().constData(), + reinterpret_cast( &JobPrivate::gnomeKeyring_writeCb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + break; + + case Backend_Kwallet4: + kwalletWritePasswordScheduledStart("org.kde.kwalletd", "/modules/kwalletd", this); + break; + case Backend_Kwallet5: + kwalletWritePasswordScheduledStart("org.kde.kwalletd5", "/modules/kwalletd5", this); + break; + } +} + +void DeletePasswordJobPrivate::fallbackOnError(const QDBusError &err) { + QScopedPointer local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.data(); + + if ( !q->insecureFallback() ) { + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2") + .arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + actual->remove( key ); + actual->sync(); + + q->emitFinished(); + + + q->emitFinished(); +} diff --git a/client/qtkeychain/keychain_win.cpp b/client/qtkeychain/keychain_win.cpp new file mode 100644 index 0000000..2366b47 --- /dev/null +++ b/client/qtkeychain/keychain_win.cpp @@ -0,0 +1,188 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" +#include "plaintextstore_p.h" + +#include +#include + +#include + +using namespace QKeychain; + +#if defined(USE_CREDENTIAL_STORE) +#include + +void ReadPasswordJobPrivate::scheduledStart() { + LPCWSTR name = (LPCWSTR)key.utf16(); + PCREDENTIALW cred; + + if (!CredReadW(name, CRED_TYPE_GENERIC, 0, &cred)) { + Error error; + QString msg; + switch(GetLastError()) { + case ERROR_NOT_FOUND: + error = EntryNotFound; + msg = tr("Password entry not found"); + break; + default: + error = OtherError; + msg = tr("Could not decrypt data"); + break; + } + + q->emitFinishedWithError( error, msg ); + return; + } + + data = QByteArray((char*)cred->CredentialBlob, cred->CredentialBlobSize); + CredFree(cred); + + q->emitFinished(); +} + +void WritePasswordJobPrivate::scheduledStart() { + CREDENTIALW cred; + char *pwd = data.data(); + LPWSTR name = (LPWSTR)key.utf16(); + + memset(&cred, 0, sizeof(cred)); + cred.Comment = const_cast(L"QtKeychain"); + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = name; + cred.CredentialBlobSize = data.size(); + cred.CredentialBlob = (LPBYTE)pwd; + cred.Persist = CRED_PERSIST_ENTERPRISE; + + if (CredWriteW(&cred, 0)) { + q->emitFinished(); + return; + } + + DWORD err = GetLastError(); + + // Detect size-exceeded errors and provide nicer messages. + // Unfortunately these error codes aren't documented. + // Found empirically on Win10 1803 build 17134.523. + if (err == RPC_X_BAD_STUB_DATA) { + const size_t maxBlob = CRED_MAX_CREDENTIAL_BLOB_SIZE; + if (cred.CredentialBlobSize > maxBlob) { + q->emitFinishedWithError( + OtherError, + tr("Credential size exceeds maximum size of %1").arg(maxBlob)); + return; + } + } + if (err == RPC_S_INVALID_BOUND) { + const size_t maxTargetName = CRED_MAX_GENERIC_TARGET_NAME_LENGTH; + if (key.size() > maxTargetName) { + q->emitFinishedWithError( + OtherError, + tr("Credential key exceeds maximum size of %1").arg(maxTargetName)); + return; + } + } + + q->emitFinishedWithError( OtherError, tr("Writing credentials failed: Win32 error code %1").arg(err) ); +} + +void DeletePasswordJobPrivate::scheduledStart() { + LPCWSTR name = (LPCWSTR)key.utf16(); + + if (!CredDeleteW(name, CRED_TYPE_GENERIC, 0)) { + Error error; + QString msg; + switch(GetLastError()) { + case ERROR_NOT_FOUND: + error = EntryNotFound; + msg = tr("Password entry not found"); + break; + default: + error = OtherError; + msg = tr("Could not decrypt data"); + break; + } + + q->emitFinishedWithError( error, msg ); + } else { + q->emitFinished(); + } +} +#else +void ReadPasswordJobPrivate::scheduledStart() { + PlainTextStore plainTextStore( q->service(), q->settings() ); + QByteArray encrypted = plainTextStore.readData( key ); + if ( plainTextStore.error() != NoError ) { + q->emitFinishedWithError( plainTextStore.error(), plainTextStore.errorString() ); + return; + } + + DATA_BLOB blob_in, blob_out; + + blob_in.pbData = reinterpret_cast( encrypted.data() ); + blob_in.cbData = encrypted.size(); + + const BOOL ret = CryptUnprotectData( &blob_in, + NULL, + NULL, + NULL, + NULL, + 0, + &blob_out ); + if ( !ret ) { + q->emitFinishedWithError( OtherError, tr("Could not decrypt data") ); + return; + } + + data = QByteArray( reinterpret_cast( blob_out.pbData ), blob_out.cbData ); + SecureZeroMemory( blob_out.pbData, blob_out.cbData ); + LocalFree( blob_out.pbData ); + + q->emitFinished(); +} + +void WritePasswordJobPrivate::scheduledStart() { + DATA_BLOB blob_in, blob_out; + blob_in.pbData = reinterpret_cast( data.data() ); + blob_in.cbData = data.size(); + const BOOL res = CryptProtectData( &blob_in, + L"QKeychain-encrypted data", + NULL, + NULL, + NULL, + 0, + &blob_out ); + if ( !res ) { + q->emitFinishedWithError( OtherError, tr("Encryption failed") ); //TODO more details available? + return; + } + + const QByteArray encrypted( reinterpret_cast( blob_out.pbData ), blob_out.cbData ); + LocalFree( blob_out.pbData ); + + PlainTextStore plainTextStore( q->service(), q->settings() ); + plainTextStore.write( key, encrypted, Binary ); + if ( plainTextStore.error() != NoError ) { + q->emitFinishedWithError( plainTextStore.error(), plainTextStore.errorString() ); + return; + } + + q->emitFinished(); +} + +void DeletePasswordJobPrivate::scheduledStart() { + PlainTextStore plainTextStore( q->service(), q->settings() ); + plainTextStore.remove( key ); + if ( plainTextStore.error() != NoError ) { + q->emitFinishedWithError( plainTextStore.error(), plainTextStore.errorString() ); + } else { + q->emitFinished(); + } +} +#endif diff --git a/client/qtkeychain/libsecret.cpp b/client/qtkeychain/libsecret.cpp new file mode 100644 index 0000000..aed91a0 --- /dev/null +++ b/client/qtkeychain/libsecret.cpp @@ -0,0 +1,336 @@ +#if defined(HAVE_LIBSECRET) +#include +#endif + +#include "libsecret_p.h" + +#include +#include + +#if defined(HAVE_LIBSECRET) +const SecretSchema* qtkeychainSchema(void) { + static const SecretSchema schema = { + "org.qt.keychain", SECRET_SCHEMA_DONT_MATCH_NAME, + { + { "user", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "type", SECRET_SCHEMA_ATTRIBUTE_STRING } + } + }; + + return &schema; +} + +typedef struct { + QKeychain::JobPrivate *self; + QString user; + QString server; +} callbackArg; + +typedef void (*secret_password_lookup_t) (const SecretSchema *schema, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + ...) G_GNUC_NULL_TERMINATED; +typedef gchar *(*secret_password_lookup_finish_t) (GAsyncResult *result, + GError **error); +typedef void (*secret_password_store_t) (const SecretSchema *schema, + const gchar *collection, + const gchar *label, + const gchar *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + ...) G_GNUC_NULL_TERMINATED; +typedef gboolean (*secret_password_store_finish_t) (GAsyncResult *result, + GError **error); +typedef void (*secret_password_clear_t) (const SecretSchema *schema, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + ...) G_GNUC_NULL_TERMINATED; +typedef gboolean (*secret_password_clear_finish_t) (GAsyncResult *result, + GError **error); +typedef void (*secret_password_free_t) (gchar *password); +typedef GQuark (*secret_error_get_quark_t) (void) G_GNUC_CONST; + +static secret_password_lookup_t secret_password_lookup_fn = NULL; +static secret_password_lookup_finish_t secret_password_lookup_finish_fn = NULL; +static secret_password_store_t secret_password_store_fn = NULL; +static secret_password_store_finish_t secret_password_store_finish_fn = NULL; +static secret_password_clear_t secret_password_clear_fn = NULL; +static secret_password_clear_finish_t secret_password_clear_finish_fn = NULL; +static secret_password_free_t secret_password_free_fn = NULL; +static secret_error_get_quark_t secret_error_get_quark_fn = NULL; + +static QKeychain::Error gerrorToCode(const GError *error) { + if (error->domain != secret_error_get_quark_fn()) { + return QKeychain::OtherError; + } + + switch(error->code) { + case SECRET_ERROR_NO_SUCH_OBJECT: + return QKeychain::EntryNotFound; + case SECRET_ERROR_IS_LOCKED: + return QKeychain::AccessDenied; + default: + return QKeychain::OtherError; + } +} + +static void +on_password_lookup (GObject *source, + GAsyncResult *result, + gpointer inst) +{ + GError *error = NULL; + callbackArg *arg = (callbackArg*)inst; + gchar *password = secret_password_lookup_finish_fn (result, &error); + + Q_UNUSED(source); + + if (arg) { + if (error) { + QKeychain::Error code = gerrorToCode(error); + + arg->self->q->emitFinishedWithError( code, QString::fromUtf8(error->message) ); + } else { + if (password != NULL) { + QByteArray raw = QByteArray(password); + switch(arg->self->mode) { + case QKeychain::JobPrivate::Binary: + arg->self->data = QByteArray::fromBase64(raw); + break; + case QKeychain::JobPrivate::Text: + default: + arg->self->data = raw; + } + + arg->self->q->emitFinished(); + } else if (arg->self->mode == QKeychain::JobPrivate::Text) { + arg->self->mode = QKeychain::JobPrivate::Binary; + secret_password_lookup_fn (qtkeychainSchema(), NULL, + on_password_lookup, arg, + "user", arg->user.toUtf8().constData(), + "server", arg->server.toUtf8().constData(), + "type", "base64", + NULL); + return; + } else { + arg->self->q->emitFinishedWithError( QKeychain::EntryNotFound, QObject::tr("Entry not found") ); + } + } + } + if (error) { + g_error_free (error); + } + + if (password) { + secret_password_free_fn (password); + } + + if (arg) { + delete arg; + } +} + +static void +on_password_stored (GObject *source, + GAsyncResult *result, + gpointer inst) +{ + GError *error = NULL; + QKeychain::JobPrivate *self = (QKeychain::JobPrivate*)inst; + + Q_UNUSED(source); + + secret_password_store_finish_fn (result, &error); + + if (self) { + if (error != NULL) { + self->q->emitFinishedWithError( gerrorToCode(error), + QString::fromUtf8(error->message) ); + } else { + self->q->emitFinished(); + } + } + if (error != NULL) { + g_error_free (error); + } +} + +static void +on_password_cleared (GObject *source, + GAsyncResult *result, + gpointer inst) +{ + GError *error = NULL; + QKeychain::JobPrivate *self = (QKeychain::JobPrivate*)inst; + gboolean removed = secret_password_clear_finish_fn (result, &error); + + Q_UNUSED(source); + if (self) { + if ( error ) { + self->q->emitFinishedWithError( gerrorToCode(error), + QString::fromUtf8(error->message) ); + } else { + Q_UNUSED(removed); + self->q->emitFinished(); + } + } + if (error != NULL) { + g_error_free (error); + } +} + +static QString modeToString(QKeychain::JobPrivate::Mode mode) { + switch(mode) { + case QKeychain::JobPrivate::Binary: + return "base64"; + default: + return "plaintext"; + } +} +#endif + +bool LibSecretKeyring::isAvailable() { +#if defined(HAVE_LIBSECRET) + const LibSecretKeyring& keyring = instance(); + if (!keyring.isLoaded()) + return false; + if (secret_password_lookup_fn == NULL) + return false; + if (secret_password_lookup_finish_fn == NULL) + return false; + if (secret_password_store_fn == NULL) + return false; + if (secret_password_store_finish_fn == NULL) + return false; + if (secret_password_clear_fn == NULL) + return false; + if (secret_password_clear_finish_fn == NULL) + return false; + if (secret_password_free_fn == NULL) + return false; + if (secret_error_get_quark_fn == NULL) + return false; + return true; +#else + return false; +#endif +} + +bool LibSecretKeyring::findPassword(const QString &user, const QString &server, + QKeychain::JobPrivate *self) +{ +#if defined(HAVE_LIBSECRET) + if (!isAvailable()) { + return false; + } + + self->mode = QKeychain::JobPrivate::Text; + self->data = QByteArray(); + + callbackArg *arg = new callbackArg; + arg->self = self; + arg->user = user; + arg->server = server; + + qDebug() << Q_FUNC_INFO; + secret_password_lookup_fn (qtkeychainSchema(), NULL, on_password_lookup, arg, + "user", user.toUtf8().constData(), + "server", server.toUtf8().constData(), + "type", "plaintext", + NULL); + return true; +#else + return false; +#endif +} + +bool LibSecretKeyring::writePassword(const QString &display_name, + const QString &user, + const QString &server, + const QKeychain::JobPrivate::Mode mode, + const QByteArray &password, + QKeychain::JobPrivate *self) +{ +#if defined(HAVE_LIBSECRET) + if (!isAvailable()) { + return false; + } + + QString type = modeToString(mode); + QByteArray pwd; + switch(mode) { + case QKeychain::JobPrivate::Binary: + pwd = password.toBase64(); + break; + default: + pwd = password; + break; + } + + qDebug() << Q_FUNC_INFO; + secret_password_store_fn (qtkeychainSchema(), SECRET_COLLECTION_DEFAULT, + display_name.toUtf8().constData(), + pwd.constData(), NULL, on_password_stored, self, + "user", user.toUtf8().constData(), + "server", server.toUtf8().constData(), + "type", type.toUtf8().constData(), + NULL); + return true; +#else + return false; +#endif +} + +bool LibSecretKeyring::deletePassword(const QString &key, const QString &service, + QKeychain::JobPrivate* self) +{ +#if defined(HAVE_LIBSECRET) + if (!isAvailable()) { + return false; + } + + qDebug() << Q_FUNC_INFO; + secret_password_clear_fn (qtkeychainSchema(), NULL, on_password_cleared, self, + "user", key.toUtf8().constData(), + "server", service.toUtf8().constData(), + NULL); + return true; +#else + return false; +#endif +} + +LibSecretKeyring::LibSecretKeyring() + : QLibrary(QStringLiteral("secret-1"), 0) +{ +#ifdef HAVE_LIBSECRET + if (load()) { + secret_password_lookup_fn = + (secret_password_lookup_t)resolve("secret_password_lookup"); + secret_password_lookup_finish_fn = + (secret_password_lookup_finish_t)resolve("secret_password_lookup_finish"); + secret_password_store_fn = + (secret_password_store_t)resolve("secret_password_store"); + secret_password_store_finish_fn = + (secret_password_store_finish_t)resolve("secret_password_store_finish"); + secret_password_clear_fn = + (secret_password_clear_t)resolve("secret_password_clear"); + secret_password_clear_finish_fn = + (secret_password_clear_finish_t)resolve("secret_password_clear_finish"); + secret_password_free_fn = + (secret_password_free_t)resolve("secret_password_free"); + secret_error_get_quark_fn = + (secret_error_get_quark_t)resolve("secret_error_get_quark"); + } +#endif +} + +LibSecretKeyring &LibSecretKeyring::instance() { + static LibSecretKeyring instance; + + return instance; +} diff --git a/client/qtkeychain/libsecret_p.h b/client/qtkeychain/libsecret_p.h new file mode 100644 index 0000000..bd966fe --- /dev/null +++ b/client/qtkeychain/libsecret_p.h @@ -0,0 +1,33 @@ +#ifndef QTKEYCHAIN_LIBSECRET_P_H +#define QTKEYCHAIN_LIBSECRET_P_H + +#include + +#include "keychain_p.h" + +class LibSecretKeyring : public QLibrary { +public: + static bool isAvailable(); + + static bool findPassword(const QString& user, + const QString& server, + QKeychain::JobPrivate* self); + + static bool writePassword(const QString& display_name, + const QString& user, + const QString& server, + const QKeychain::JobPrivate::Mode type, + const QByteArray& password, + QKeychain::JobPrivate* self); + + static bool deletePassword(const QString &key, const QString &service, + QKeychain::JobPrivate* self); + +private: + LibSecretKeyring(); + + static LibSecretKeyring &instance(); +}; + + +#endif diff --git a/client/qtkeychain/org.kde.KWallet.xml b/client/qtkeychain/org.kde.KWallet.xml new file mode 100644 index 0000000..548c2f8 --- /dev/null +++ b/client/qtkeychain/org.kde.KWallet.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/qtkeychain/plaintextstore.cpp b/client/qtkeychain/plaintextstore.cpp new file mode 100644 index 0000000..b9d0272 --- /dev/null +++ b/client/qtkeychain/plaintextstore.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * Copyright (C) 2016 Mathias Hasselmann * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ + +#include "plaintextstore_p.h" + +using namespace QKeychain; + +namespace { +#ifdef Q_OS_WIN +inline QString dataKey(const QString &key) { return key; } +#else // Q_OS_WIN +inline QString dataKey(const QString &key) { return key + QLatin1String("/data"); } +inline QString typeKey(const QString &key) { return key + QLatin1String("/type"); } +#endif // Q_OS_WIN +} + + +PlainTextStore::PlainTextStore(const QString &service, QSettings *settings) + : m_localSettings(settings ? 0 : new QSettings(service)) + , m_actualSettings(settings ? settings : m_localSettings.data()) + , m_error(NoError) +{ +} + +bool PlainTextStore::contains(const QString &key) const +{ + return m_actualSettings->contains(dataKey(key)); +} + +QByteArray PlainTextStore::readData(const QString &key) +{ + return read(dataKey(key)).toByteArray(); +} + +#ifndef Q_OS_WIN + +JobPrivate::Mode PlainTextStore::readMode(const QString &key) +{ + return JobPrivate::stringToMode(read(typeKey(key)).toString()); +} + +#endif // Q_OS_WIN + +void PlainTextStore::write(const QString &key, const QByteArray &data, JobPrivate::Mode mode) +{ + if (m_actualSettings->status() != QSettings::NoError) + return; + +#ifndef Q_OS_WIN + m_actualSettings->setValue(typeKey(key), JobPrivate::modeToString(mode)); +#else // Q_OS_WIN + Q_UNUSED(mode); +#endif // Q_OS_WIN + m_actualSettings->setValue(dataKey(key), data); + m_actualSettings->sync(); + + if (m_actualSettings->status() == QSettings::AccessError) { + setError(AccessDenied, tr("Could not store data in settings: access error")); + } else if (m_actualSettings->status() != QSettings::NoError) { + setError(OtherError, tr("Could not store data in settings: format error")); + } else { + setError(NoError, QString()); + } +} + +void PlainTextStore::remove(const QString &key) +{ + if (m_actualSettings->status() != QSettings::NoError) + return; + +#ifndef Q_OS_WIN + m_actualSettings->remove(typeKey(key)); +#endif // Q_OS_WIN + m_actualSettings->remove(dataKey(key)); + m_actualSettings->sync(); + + if (m_actualSettings->status() == QSettings::AccessError) { + setError(AccessDenied, tr("Could not delete data from settings: access error")); + } else if (m_actualSettings->status() != QSettings::NoError) { + setError(OtherError, tr("Could not delete data from settings: format error")); + } else { + setError(NoError, QString()); + } +} + +void PlainTextStore::setError(Error error, const QString &errorString) +{ + m_error = error; + m_errorString = errorString; +} + +QVariant PlainTextStore::read(const QString &key) +{ + const QVariant value = m_actualSettings->value(key); + + if (value.isNull()) { + setError(EntryNotFound, tr("Entry not found")); + } else { + setError(NoError, QString()); + } + + return value; +} diff --git a/client/qtkeychain/plaintextstore_p.h b/client/qtkeychain/plaintextstore_p.h new file mode 100644 index 0000000..7ec05aa --- /dev/null +++ b/client/qtkeychain/plaintextstore_p.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * Copyright (C) 2016 Mathias Hasselmann * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ + +#ifndef QTKEYCHAIN_PLAINTEXTSTORE_P_H +#define QTKEYCHAIN_PLAINTEXTSTORE_P_H + +#include "keychain_p.h" + +namespace QKeychain { + +class PlainTextStore { + Q_DECLARE_TR_FUNCTIONS(QKeychain::PlainTextStore) + +public: + explicit PlainTextStore(const QString &service, QSettings *settings); + + Error error() const { return m_error; } + QString errorString() const { return m_errorString; } + + bool contains(const QString &key) const; + + QByteArray readData(const QString &key); + JobPrivate::Mode readMode(const QString &key); + + void write(const QString &key, const QByteArray &data, JobPrivate::Mode mode); + void remove(const QString &key); + +private: + void setError(Error error, const QString &errorString); + QVariant read(const QString &key); + + const QScopedPointer m_localSettings; + QSettings *const m_actualSettings; + QString m_errorString; + Error m_error; +}; + +} + +#endif // QTKEYCHAIN_PLAINTEXTSTORE_P_H diff --git a/client/qtkeychain/qt5keychain.pri b/client/qtkeychain/qt5keychain.pri new file mode 100644 index 0000000..ee6d4e8 --- /dev/null +++ b/client/qtkeychain/qt5keychain.pri @@ -0,0 +1,83 @@ +# Minimal qmake support. +# This file is provided as is without any warranty. +# It can break at anytime or be removed without notice. + +QT5KEYCHAIN_PWD = $$PWD + +CONFIG += depend_includepath +DEFINES += QTKEYCHAIN_NO_EXPORT + +INCLUDEPATH += \ + $$PWD/.. \ + $$QT5KEYCHAIN_PWD + +HEADERS += \ + $$QT5KEYCHAIN_PWD/keychain_p.h \ + $$QT5KEYCHAIN_PWD/keychain.h + +SOURCES += \ + $$QT5KEYCHAIN_PWD/keychain.cpp + +unix:!macx:!ios { + # Remove the following LIBSECRET_SUPPORT line + # to build without libsecret support. + DEFINES += LIBSECRET_SUPPORT + contains(DEFINES, LIBSECRET_SUPPORT) { + packagesExist(libsecret-1) { + !build_pass:message("Libsecret support: on") + CONFIG += link_pkgconfig + PKGCONFIG += libsecret-1 + DEFINES += HAVE_LIBSECRET + } else { + !build_pass:warning("Libsecret not found.") + !build_pass:message("Libsecret support: off") + } + } else { + !build_pass:message("Libsecret support: off") + } + + # Generate D-Bus interface: + QT += dbus + kwallet_interface.files = $$PWD/org.kde.KWallet.xml + DBUS_INTERFACES += kwallet_interface + + HEADERS += \ + $$QT5KEYCHAIN_PWD/gnomekeyring_p.h \ + $$QT5KEYCHAIN_PWD/plaintextstore_p.h \ + $$QT5KEYCHAIN_PWD/libsecret_p.h + SOURCES += \ + $$QT5KEYCHAIN_PWD/keychain_unix.cpp \ + $$QT5KEYCHAIN_PWD/plaintextstore.cpp \ + $$QT5KEYCHAIN_PWD/gnomekeyring.cpp \ + $$QT5KEYCHAIN_PWD/libsecret.cpp +} + +win32 { + # Remove the following USE_CREDENTIAL_STORE line + # to use the CryptProtectData Windows API function + # instead of the Windows Credential Store. + DEFINES += USE_CREDENTIAL_STORE + contains(DEFINES, USE_CREDENTIAL_STORE) { + !build_pass:message("Windows Credential Store support: on") + LIBS += -lAdvapi32 + } else { + !build_pass:message("Windows Credential Store support: off") + LIBS += -lCrypt32 + HEADERS += $$QT5KEYCHAIN_PWD/plaintextstore_p.h + SOURCES += $$QT5KEYCHAIN_PWD/plaintextstore.cpp + } + HEADERS += $$QT5KEYCHAIN_PWD/libsecret_p.h + SOURCES += \ + $$QT5KEYCHAIN_PWD/keychain_win.cpp \ + $$QT5KEYCHAIN_PWD/libsecret.cpp +} + +macx:!ios { + LIBS += "-framework Security" "-framework Foundation" + SOURCES += $$QT5KEYCHAIN_PWD/keychain_mac.cpp +} + +ios { + LIBS += "-framework Security" "-framework Foundation" + OBJECTIVE_SOURCES += $$QT5KEYCHAIN_PWD/keychain_ios.mm +} diff --git a/client/qtkeychain/testclient.cpp b/client/qtkeychain/testclient.cpp new file mode 100644 index 0000000..94189cf --- /dev/null +++ b/client/qtkeychain/testclient.cpp @@ -0,0 +1,121 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include +#include + +#include "keychain.h" +#include + +using namespace QKeychain; + +static int printUsage() { + std::cerr << "testclient store " << std::endl; + std::cerr << "testclient restore " << std::endl; + std::cerr << "testclient delete " << std::endl; + return 1; +} + +int main( int argc, char** argv ) { + QCoreApplication app( argc, argv ); + const QStringList args = app.arguments(); + if ( args.count() < 2 ) + return printUsage(); + + QStringList::ConstIterator it = args.constBegin(); + ++it; + + if ( *it == QLatin1String("store") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it == args.constEnd() ) + return printUsage(); + const QString pass = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + WritePasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + job.setTextData( pass ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + if ( job.error() ) { + std::cerr << "Storing password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << "Password stored successfully" << std::endl; + } else if ( *it == QLatin1String("bstore") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it == args.constEnd() ) + return printUsage(); + const QString pass = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + WritePasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + job.setBinaryData( pass.toUtf8() ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + if ( job.error() ) { + std::cerr << "Storing binary password failed: " + << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << "Password stored successfully" << std::endl; + } else if ( *it == QLatin1String("restore") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + ReadPasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + + const QString pw = job.textData(); + if ( job.error() ) { + std::cerr << "Restoring password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << qPrintable(pw) << std::endl; + } else if ( *it == QLatin1String("delete") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + DeletePasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + + if ( job.error() ) { + std::cerr << "Deleting password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << "Password deleted successfully" << std::endl; + } else { + return printUsage(); + } +} + diff --git a/client/qtkeychain/translations/qtkeychain_de.ts b/client/qtkeychain/translations/qtkeychain_de.ts new file mode 100644 index 0000000..6610c0a --- /dev/null +++ b/client/qtkeychain/translations/qtkeychain_de.ts @@ -0,0 +1,177 @@ + + + + + QKeychain::ReadPasswordJobPrivate + + + Unknown error + Unbekannter Fehler + + + + D-Bus is not running + + + + + No keychain service available + Kein Schlüsselbund-Dienst verfügbar + + + + Could not open wallet: %1; %2 + Konnte Brieftasche nicht öffnen: %1; %2 + + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + Could not determine data type: %1; %2 + Datentyp kann nicht ermittelt werden: %1: %2 + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Could not read password: %1; %2 + Passwort konnte nicht ausgelesen werden: %1; %2 + + + + Password not found + Passwort nicht gefunden + + + + + Entry not found + Eintrag nicht gefunden + + + + Could not decrypt data + Kann Daten nicht entschlüsseln + + + + QKeychain::WritePasswordJobPrivate + + + + Unknown error + Unbekannter Fehler + + + + D-Bus is not running + + + + + + Could not open wallet: %1; %2 + Konnte Brieftasche nicht öffnen: %1; %2 + + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + Could not delete encrypted data from settings: access error + Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Zugriffsfehler + + + + Could not delete encrypted data from settings: format error + Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Formatfehler + + + + Encryption failed + Verschlüsselung fehlgeschlagen + + + + Could not store encrypted data in settings: access error + Kann verschlüsselte Daten nicht in den Einstellungen speichern: Zugriffsfehler + + + + Could not store encrypted data in settings: format error + Kann verschlüsselte Daten nicht in den Einstellungen speichern: Formatfehler + + + + QObject + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + No keyring daemon + Kein Schlüsselbund-Dienst + + + + Already unlocked + Bereits entsperrt + + + + No such keyring + Kein solcher Schlüsselbund + + + + Bad arguments + Ungültige Argumente + + + + I/O error + Ein-/Ausgabe-Fehler + + + + Cancelled + Abgebrochen + + + + Keyring already exists + Schlüsselbund existiert bereits + + + + No match + Kein Treffer + + + + Unknown error + Unbekannter Fehler + + + + + %1 (OSStatus %2) + + + + diff --git a/client/qtkeychain/translations/qtkeychain_ro.ts b/client/qtkeychain/translations/qtkeychain_ro.ts new file mode 100644 index 0000000..337f1aa --- /dev/null +++ b/client/qtkeychain/translations/qtkeychain_ro.ts @@ -0,0 +1,178 @@ + + + + + QKeychain::ReadPasswordJobPrivate + + + Unknown error + Eroare necunoscută + + + + D-Bus is not running + D-Bus nu rulează + + + + No keychain service available + Nu există niciun serviciu de chei disponibil + Kein Schlüsselbund-Dienst verfügbar + + + + Could not open wallet: %1; %2 + Nu se poate deschide portofelul: %1; %2 + + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + Could not determine data type: %1; %2 + Nu se poate stabili tipul de date: %1: %2 + + + + Unsupported entry type 'Map' + Tip de înregistrare nesuportat 'Map' + + + + Unknown kwallet entry type '%1' + Tip de înregistrare kwallet necunoscut '%1' + + + + Could not read password: %1; %2 + Nu se poate citi parola: %1; %2 + + + + Password not found + Parola nu a fost găsită + + + + + Entry not found + Înregistrarea nu a fost găsită + + + + Could not decrypt data + Nu se poate decripta data + + + + QKeychain::WritePasswordJobPrivate + + + + Unknown error + Eroare necunoscută + + + + D-Bus is not running + D-Bus nu rulează + + + + + Could not open wallet: %1; %2 + Nu se poate deschide portofelul: %1; %2 + + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + Could not delete encrypted data from settings: access error + Nu se pot șterge datele criptate din setări: eroare de acces + + + + Could not delete encrypted data from settings: format error + Nu se pot șterge datele criptate din setări: eroare de format + + + + Encryption failed + Criptarea a eșuat + + + + Could not store encrypted data in settings: access error + Nu se pot stoca datele criptate în setări: eroare de acces + + + + Could not store encrypted data in settings: format error + Nu se pot stoca datele criptate în setări: eroare de format + + + + QObject + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + No keyring daemon + Niciun demon pentru inelul de chei + + + + Already unlocked + Deja deblocat + + + + No such keyring + Nu există astfel de inel de chei + + + + Bad arguments + Argumente greșite + + + + I/O error + Eroare de I/E + + + + Cancelled + Anulat + + + + Keyring already exists + Inelul de chei deja există + + + + No match + Nicio potrivire + + + + Unknown error + Eroare necunoscută + + + + + %1 (OSStatus %2) + %1 (OSStatus %2) + + + diff --git a/client/qtkeychain/translations/qtkeychain_zh.ts b/client/qtkeychain/translations/qtkeychain_zh.ts new file mode 100644 index 0000000..1e22eca --- /dev/null +++ b/client/qtkeychain/translations/qtkeychain_zh.ts @@ -0,0 +1,177 @@ + + + + + QKeychain::ReadPasswordJobPrivate + + + Unknown error + 未知的錯誤 + + + + D-Bus is not running + D-Bus 不在執行中 + + + + No keychain service available + 沒有可用的鑰匙圈服務 + + + + Could not open wallet: %1; %2 + 無法開啟錢包:%1; %2 + + + + Access to keychain denied + 鑰匙圈存取被拒絕 + + + + Could not determine data type: %1; %2 + 無法判斷資料型別:%1; %2 + + + + Unsupported entry type 'Map' + 不支援的項目類型 'Map' + + + + Unknown kwallet entry type '%1' + 未知的 kwallet 項目類型 '%1' + + + + Could not read password: %1; %2 + 無法讀取密碼:%1; %2 + + + + Password not found + 找不到密碼 + + + + + Entry not found + 找不到項目 + + + + Could not decrypt data + 無法解密資料 + + + + QKeychain::WritePasswordJobPrivate + + + + Unknown error + 未知的錯誤 + + + + D-Bus is not running + D-Bus 不在執行中 + + + + + Could not open wallet: %1; %2 + 無法開啟錢包:%1; %2 + + + + Access to keychain denied + 鑰匙圈存取被拒絕 + + + + Could not delete encrypted data from settings: access error + 無法從設定刪除加密資料:存取錯誤 + + + + Could not delete encrypted data from settings: format error + 無法從設定刪除加密資料:格式錯誤 + + + + Encryption failed + 加密失敗 + + + + Could not store encrypted data in settings: access error + 無法將加密資料儲存至設定:存取錯誤 + + + + Could not store encrypted data in settings: format error + 無法將加密資料儲存至設定:格式錯誤 + + + + QObject + + + Access to keychain denied + 鑰匙圈存取被拒絕 + + + + No keyring daemon + 沒有可用的鑰匙圈背景程式 + + + + Already unlocked + 已解鎖 + + + + No such keyring + 鑰匙圈不存在 + + + + Bad arguments + 引數錯誤 + + + + I/O error + I/O 錯誤 + + + + Cancelled + 已取消 + + + + Keyring already exists + 鑰匙圈已存在 + + + + No match + 無相符項目 + + + + Unknown error + 未知的錯誤 + + + + + %1 (OSStatus %2) + %1 (OSStatus %2) + + +