diff --git a/CHANGELOG.md b/CHANGELOG.md index d281fb8..b993919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## Squawk 0.1.4 (UNRELEASED) ------------------------------- ### New features - several ways to manage your account password: - store it in plain text with the config (like it always was) @@ -14,7 +13,6 @@ ## Squawk 0.1.3 (Mar 31, 2020) ------------------------------- ### New features - delivery states for the messages - delivery receipts now work for real @@ -32,7 +30,6 @@ ## Squawk 0.1.2 (Dec 25, 2019) ------------------------------- ### New features - icons in roster for conferences - pal avatar in dialog window @@ -50,7 +47,6 @@ ## Squawk 0.1.1 (Nov 16, 2019) ------------------------------- A lot of bug fixes, memory leaks fixes ### New features - exchange files via HTTP File Upload @@ -60,7 +56,6 @@ A lot of bug fixes, memory leaks fixes ## Squawk 0.0.5 (Oct 10, 2019) ------------------------------- ### Features - chat directly - have multiple accounts diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index ad37198..32d61b9 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -23,13 +23,14 @@ set(squawkCORE_SRC # Tell CMake to create the helloworld executable add_library(squawkCORE ${squawkCORE_SRC}) +add_subdirectory(passwordStorageEngines) + if(SYSTEM_QXMPP) find_package(QXmpp CONFIG REQUIRED) get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) endif() - # Use the Widgets module from Qt 5. target_link_libraries(squawkCORE Qt5::Core) target_link_libraries(squawkCORE Qt5::Network) @@ -38,3 +39,4 @@ target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) target_link_libraries(squawkCORE simpleCrypt) +target_link_libraries(squawkCORE kwalletPSE) diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt new file mode 100644 index 0000000..9527238 --- /dev/null +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.0) +project(pse) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(KF5Wallet CONFIG REQUIRED) + +get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) + +set(kwalletPSE_SRC + kwallet.cpp +) + +add_library(kwalletPSE ${kwalletPSE_SRC}) + +target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + +target_link_libraries(kwalletPSE Qt5::Core) + +set(kwalletW_SRC + wrappers/kwallet.cpp +) + +add_library(kwalletWrapper SHARED ${kwalletW_SRC}) + +target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + +target_link_libraries(kwalletWrapper KF5::Wallet) +target_link_libraries(kwalletWrapper Qt5::Core) + +install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp new file mode 100644 index 0000000..ddfb466 --- /dev/null +++ b/core/passwordStorageEngines/kwallet.cpp @@ -0,0 +1,227 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "kwallet.h" + +Core::PSE::KWallet::OpenWallet Core::PSE::KWallet::openWallet = 0; +Core::PSE::KWallet::NetworkWallet Core::PSE::KWallet::networkWallet = 0; +Core::PSE::KWallet::DeleteWallet Core::PSE::KWallet::deleteWallet = 0; +Core::PSE::KWallet::ReadPassword Core::PSE::KWallet::readPassword = 0; +Core::PSE::KWallet::WritePassword Core::PSE::KWallet::writePassword = 0; +Core::PSE::KWallet::HasFolder Core::PSE::KWallet::hasFolder = 0; +Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0; +Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0; + +Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial; +QLibrary Core::PSE::KWallet::lib("./libkwalletWrapper.so"); + +Core::PSE::KWallet::KWallet(): + QObject(), + cState(disconnected), + everError(false), + wallet(0), + readRequest(), + writeRequest() +{ + if (sState == initial) { + lib.load(); + + if (lib.isLoaded()) { + openWallet = (OpenWallet) lib.resolve("openWallet"); + networkWallet = (NetworkWallet) lib.resolve("networkWallet"); + deleteWallet = (DeleteWallet) lib.resolve("deleteWallet"); + readPassword = (ReadPassword) lib.resolve("readPassword"); + writePassword = (WritePassword) lib.resolve("writePassword"); + hasFolder = (HasFolder) lib.resolve("hasFolder"); + createFolder = (CreateFolder) lib.resolve("createFolder"); + setFolder = (SetFolder) lib.resolve("setFolder"); + + if (openWallet + && networkWallet + && deleteWallet + && readPassword + && writePassword + ) { + sState = success; + } else { + sState = failure; + } + } else { + sState = failure; + } + } +} + +Core::PSE::KWallet::~KWallet() +{ + close(); +} + +void Core::PSE::KWallet::open() +{ + if (sState == success) { + if (cState == disconnected) { + wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous); + if (wallet) { + cState = connecting; + connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool))); + connect(wallet, SIGNAL(walletClosed()), this, SLOT(onWalletClosed())); + } else { + everError = true; + emit opened(false); + } + } + } +} + +Core::PSE::KWallet::ConnectionState Core::PSE::KWallet::connectionState() +{ + return cState; +} + +void Core::PSE::KWallet::close() +{ + if (sState == success) { + if (cState != disconnected) { + deleteWallet(wallet); + wallet = 0; + } + rejectPending(); + } +} + +void Core::PSE::KWallet::onWalletClosed() +{ + cState = disconnected; + deleteWallet(wallet); + wallet = 0; + emit closed(); + rejectPending(); +} + +void Core::PSE::KWallet::onWalletOpened(bool success) +{ + emit opened(success); + if (success) { + QString appName = QCoreApplication::applicationName(); + if (!hasFolder(wallet, appName)) { + createFolder(wallet, appName); + } + setFolder(wallet, appName); + cState = connected; + readPending(); + } else { + everError = true; + cState = disconnected; + deleteWallet(wallet); + wallet = 0; + rejectPending(); + } +} + +Core::PSE::KWallet::SupportState Core::PSE::KWallet::supportState() +{ + return sState; +} + +bool Core::PSE::KWallet::everHadError() const +{ + return everError; +} + +void Core::PSE::KWallet::resetEverHadError() +{ + everError = false; +} + +void Core::PSE::KWallet::requestReadPassword(const QString& login, bool askAgain) +{ + if (sState == success) { + readRequest.insert(login); + readSwitch(askAgain); + } +} + +void Core::PSE::KWallet::requestWritePassword(const QString& login, const QString& password, bool askAgain) +{ + if (sState == success) { + std::map::iterator itr = writeRequest.find(login); + if (itr == writeRequest.end()) { + writeRequest.insert(std::make_pair(login, password)); + } else { + itr->second = password; + } + readSwitch(askAgain); + } +} + +void Core::PSE::KWallet::readSwitch(bool askAgain) +{ + switch (cState) { + case connected: + readPending(); + break; + case connecting: + break; + case disconnected: + if (!everError || askAgain) { + open(); + } + break; + } +} + +void Core::PSE::KWallet::rejectPending() +{ + writeRequest.clear(); + std::set::const_iterator i = readRequest.begin(); + while (i != readRequest.end()) { + emit rejectPassword(*i); + readRequest.erase(i); + i = readRequest.begin(); + } +} + +void Core::PSE::KWallet::readPending() +{ + std::map::const_iterator itr = writeRequest.begin(); + while (itr != writeRequest.end()) { + int result = writePassword(wallet, itr->first, itr->second); + if (result == 0) { + qDebug() << "Successfully saved password for user" << itr->first; + } else { + qDebug() << "Error writing password for user" << itr->first << ":" << result; + } + writeRequest.erase(itr); + itr = writeRequest.begin(); + } + + std::set::const_iterator i = readRequest.begin(); + while (i != readRequest.end()) { + QString password; + int result = readPassword(wallet, *i, password); + if (result == 0 && password.size() > 0) { //even though it's written that the error is supposed to be returned in case there were no password + emit responsePassword(*i, password); //it doesn't do so. I assume empty password as a lack of password in KWallet + } else { + emit rejectPassword(*i); + } + readRequest.erase(i); + i = readRequest.begin(); + } +} + diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h new file mode 100644 index 0000000..8ac52d2 --- /dev/null +++ b/core/passwordStorageEngines/kwallet.h @@ -0,0 +1,112 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CORE_PSE_KWALLET_H +#define CORE_PSE_KWALLET_H + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Core { +namespace PSE { + +/** + * @todo write docs + */ +class KWallet : public QObject +{ + Q_OBJECT +public: + enum SupportState { + initial, + success, + failure + }; + enum ConnectionState { + disconnected, + connecting, + connected + }; + + KWallet(); + ~KWallet(); + + static SupportState supportState(); + void open(); + void close(); + ConnectionState connectionState(); + bool everHadError() const; + void resetEverHadError(); + void requestReadPassword(const QString& login, bool askAgain = false); + void requestWritePassword(const QString& login, const QString& password, bool askAgain = false); + +signals: + void opened(bool success); + void closed(); + void responsePassword(const QString& login, const QString& password); + void rejectPassword(const QString& login); + +private slots: + void onWalletOpened(bool success); + void onWalletClosed(); + +private: + void readSwitch(bool askAgain); + void readPending(); + void rejectPending(); + +private: + typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType); + typedef const char* (*NetworkWallet)(); + typedef void (*DeleteWallet)(::KWallet::Wallet*); + typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&); + typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&); + typedef bool (*HasFolder)(::KWallet::Wallet* w, const QString &f); + typedef bool (*CreateFolder)(::KWallet::Wallet* w, const QString &f); + typedef bool (*SetFolder)(::KWallet::Wallet* w, const QString &f); + + static OpenWallet openWallet; + static NetworkWallet networkWallet; + static DeleteWallet deleteWallet; + static ReadPassword readPassword; + static WritePassword writePassword; + static HasFolder hasFolder; + static CreateFolder createFolder; + static SetFolder setFolder; + + static SupportState sState; + static QLibrary lib; + + ConnectionState cState; + bool everError; + ::KWallet::Wallet* wallet; + + std::set readRequest; + std::map writeRequest; +}; + +}} + +#endif // CORE_PSE_KWALLET_H diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp new file mode 100644 index 0000000..39a2eaf --- /dev/null +++ b/core/passwordStorageEngines/wrappers/kwallet.cpp @@ -0,0 +1,33 @@ +#include + +extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) { + return KWallet::Wallet::openWallet(name, w, ot); +} + +extern "C" void deleteWallet(KWallet::Wallet* w) { + w->deleteLater(); +} + +extern "C" const char* networkWallet() { + return KWallet::Wallet::NetworkWallet().toStdString().c_str(); +} + +extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) { + return w->readPassword(key, value); +} + +extern "C" int writePassword(KWallet::Wallet* w, const QString &key, const QString &value) { + return w->writePassword(key, value); +} + +extern "C" bool hasFolder(KWallet::Wallet* w, const QString &f) { + return w->hasFolder(f); +} + +extern "C" bool createFolder(KWallet::Wallet* w, const QString &f) { + return w->createFolder(f); +} + +extern "C" bool setFolder(KWallet::Wallet* w, const QString &f) { + return w->setFolder(f); +} diff --git a/core/squawk.cpp b/core/squawk.cpp index 8c70cb8..abd8977 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -27,13 +27,22 @@ Core::Squawk::Squawk(QObject* parent): accounts(), amap(), network(), - waitingForAccounts(0) + waitingForAccounts(0), + kwallet() { connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError); + + + if (kwallet.supportState() == PSE::KWallet::success) { + qDebug() << "KWallet support detected"; + connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); + connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); + connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword); + } } Core::Squawk::~Squawk() @@ -45,6 +54,11 @@ Core::Squawk::~Squawk() } } +void Core::Squawk::onWalletOpened(bool success) +{ + qDebug() << "KWallet opened: " << success; +} + void Core::Squawk::stop() { qDebug("Stopping squawk core.."); @@ -207,17 +221,27 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}}); - if (p_state == Shared::ConnectionState::disconnected) { - bool equals = true; - for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) { - if ((*itr)->getState() != Shared::ConnectionState::disconnected) { - equals = false; + switch (p_state) { + case Shared::ConnectionState::disconnected: { + bool equals = true; + for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) { + if ((*itr)->getState() != Shared::ConnectionState::disconnected) { + equals = false; + } + } + if (equals && state != Shared::Availability::offline) { + state = Shared::Availability::offline; + emit stateChanged(state); + } } - } - if (equals && state != Shared::Availability::offline) { - state = Shared::Availability::offline; - emit stateChanged(state); - } + break; + case Shared::ConnectionState::connected: + if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { + kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); + } + break; + default: + break; } } @@ -391,6 +415,13 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsetPasswordType(Shared::Global::fromInt(mItr->toInt())); } + if (acc->getPasswordType() == Shared::AccountPassword::kwallet + && kwallet.supportState() == PSE::KWallet::success + && !needToReconnect + ) { + kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); + } + emit changeAccount(name, map); } @@ -683,5 +714,29 @@ void Core::Squawk::parseAccount( addAccount(login, server, QString(), name, resource, passwordType); emit requestPassword(name); break; + case Shared::AccountPassword::kwallet: { + addAccount(login, server, QString(), name, resource, passwordType); + if (kwallet.supportState() == PSE::KWallet::success) { + kwallet.requestReadPassword(name); + } else { + emit requestPassword(name); + } + } } } + +void Core::Squawk::onWalletRejectPassword(const QString& login) +{ + emit requestPassword(login); +} + +void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password) +{ + AccountsMap::const_iterator itr = amap.find(login); + if (itr == amap.end()) { + qDebug() << "An attempt to set password to non existing account" << login << ", skipping"; + return; + } + itr->second->setPassword(password); + accountReady(); +} diff --git a/core/squawk.h b/core/squawk.h index f96f7e8..b1fa492 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -34,6 +34,8 @@ #include "networkaccess.h" #include "external/simpleCrypt/simplecrypt.h" +#include "passwordStorageEngines/kwallet.h" + namespace Core { class Squawk : public QObject @@ -114,6 +116,7 @@ private: Shared::Availability state; NetworkAccess network; uint8_t waitingForAccounts; + PSE::KWallet kwallet; private slots: void addAccount( @@ -147,6 +150,10 @@ private slots: void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data); + void onWalletOpened(bool success); + void onWalletResponsePassword(const QString& login, const QString& password); + void onWalletRejectPassword(const QString& login); + private: void readSettings(); void accountReady();