/* * 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 "squawk.h" #include #include #include #include #ifdef WITH_SIMPLE_CRYPT #include "external/simpleCrypt/simplecrypt.h" #endif Core::Squawk::Squawk(QObject* parent): QObject(parent), accounts(), amap(), state(Shared::Availability::offline), network(), isInitialized(false), #ifdef WITH_KWALLET kwallet(), #endif clientCache() { connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress); connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError); connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete); connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete); #ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword); Shared::Global::setSupported("KWallet", true); } #endif } Core::Squawk::~Squawk() { Accounts::const_iterator itr = accounts.begin(); Accounts::const_iterator end = accounts.end(); for (; itr != end; ++itr) { delete (*itr); } } void Core::Squawk::onWalletOpened(bool success) { qDebug() << "KWallet opened: " << success; } void Core::Squawk::stop() { qDebug("Stopping squawk core.."); network.stop(); clientCache.close(); if (isInitialized) { QSettings settings; settings.beginGroup("core"); settings.beginWriteArray("accounts"); for (std::deque::size_type i = 0; i < accounts.size(); ++i) { settings.setArrayIndex(i); Account* acc = accounts[i]; Shared::AccountPassword ap = acc->getPasswordType(); QString password; switch (ap) { case Shared::AccountPassword::plain: password = acc->getPassword(); break; case Shared::AccountPassword::jammed: #ifdef WITH_SIMPLE_CRYPT2 password = SimpleCrypt(passwordHash).encryptToString(acc->getPassword()); #else qDebug() << "The password for account" << acc->getName() << "is set to be jammed, but Squawk was compiled without SimpleCrypt support"; qDebug("Can not encode password, setting this account to always ask password mode"); ap = Shared::AccountPassword::alwaysAsk; #endif break; default: break; } settings.setValue("name", acc->getName()); settings.setValue("server", acc->getServer()); settings.setValue("login", acc->getLogin()); settings.setValue("password", password); settings.setValue("resource", acc->getResource()); settings.setValue("passwordType", static_cast(ap)); settings.setValue("active", acc->getActive()); } settings.endArray(); settings.endGroup(); settings.sync(); } emit quit(); } void Core::Squawk::start() { qDebug("Starting squawk core.."); readSettings(); isInitialized = true; network.start(); clientCache.open(); } void Core::Squawk::newAccountRequest(const QMap& map) { QString name = map.value("name").toString(); QString login = map.value("login").toString(); QString server = map.value("server").toString(); QString password = map.value("password").toString(); QString resource = map.value("resource").toString(); int passwordType = map.value("passwordType").toInt(); bool active = map.value("active").toBool(); addAccount(login, server, password, name, resource, active, Shared::Global::fromInt(passwordType)); } void Core::Squawk::addAccount( const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource, bool active, Shared::AccountPassword passwordType) { if (amap.count(name) > 0) { qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring"; return; } Account* acc = new Account(login, server, password, name, active, &network); acc->setResource(resource); acc->setPasswordType(passwordType); accounts.push_back(acc); amap.insert(std::make_pair(name, acc)); connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged); connect(acc, &Account::changed, this, &Squawk::onAccountChanged); connect(acc, &Account::error, this, &Squawk::onAccountError); connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword); connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged); connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact); connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup); connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup); connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact); connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence); connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence); connect(acc, &Account::message, this, &Squawk::onAccountMessage); connect(acc, &Account::changeMessage, this, &Squawk::onAccountChangeMessage); connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive); connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom); connect(acc, &Account::changeRoom, this, &Squawk::onAccountChangeRoom); connect(acc, &Account::removeRoom, this, &Squawk::onAccountRemoveRoom); connect(acc, &Account::addRoomParticipant, this, &Squawk::onAccountAddRoomPresence); connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence); connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence); connect(acc, &Account::infoReady, this, &Squawk::responseInfo); connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError); connect(acc, &Account::infoDiscovered, this, &Squawk::onAccountInfoDiscovered); QMap map = { {"login", login}, {"server", server}, {"name", name}, {"password", password}, {"resource", resource}, {"state", QVariant::fromValue(Shared::ConnectionState::disconnected)}, {"offline", QVariant::fromValue(Shared::Availability::offline)}, {"error", ""}, {"avatarPath", acc->getAvatarPath()}, {"passwordType", QVariant::fromValue(passwordType)}, {"active", active} }; emit newAccount(map); switch (passwordType) { case Shared::AccountPassword::alwaysAsk: case Shared::AccountPassword::kwallet: if (password == "") acc->invalidatePassword(); break; default: break; } if (state != Shared::Availability::offline) { acc->setAvailability(state); if (acc->getActive()) acc->connect(); } } void Core::Squawk::changeState(Shared::Availability p_state) { if (state != p_state) { for (std::deque::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) { Account* acc = *itr; acc->setAvailability(p_state); if (state == Shared::Availability::offline && acc->getActive()) acc->connect(); } state = p_state; emit stateChanged(p_state); } } void Core::Squawk::connectAccount(const QString& account) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to connect non existing account, skipping"); return; } itr->second->setActive(true); if (state != Shared::Availability::offline) itr->second->connect(); } void Core::Squawk::disconnectAccount(const QString& account) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to connect non existing account, skipping"); return; } itr->second->setActive(false); itr->second->disconnect(); } void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state) { Account* acc = static_cast(sender()); QMap changes = { {"state", QVariant::fromValue(p_state)} }; if (acc->getLastError() == Account::Error::none) changes.insert("error", ""); emit changeAccount(acc->getName(), changes); #ifdef WITH_KWALLET if (p_state == Shared::ConnectionState::connected) { if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); } } #endif } void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap& data) { Account* acc = static_cast(sender()); emit addContact(acc->getName(), jid, group, data); } void Core::Squawk::onAccountAddGroup(const QString& name) { Account* acc = static_cast(sender()); emit addGroup(acc->getName(), name); } void Core::Squawk::onAccountRemoveGroup(const QString& name) { Account* acc = static_cast(sender()); emit removeGroup(acc->getName(), name); } void Core::Squawk::onAccountChangeContact(const QString& jid, const QMap& data) { Account* acc = static_cast(sender()); emit changeContact(acc->getName(), jid, data); } void Core::Squawk::onAccountRemoveContact(const QString& jid) { Account* acc = static_cast(sender()); emit removeContact(acc->getName(), jid); } void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& group) { Account* acc = static_cast(sender()); emit removeContact(acc->getName(), jid, group); } void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap& data) { Account* acc = static_cast(sender()); emit addPresence(acc->getName(), jid, name, data); //it's equal if a MUC sends its status with presence of the same jid (ex: muc@srv.im/muc@srv.im), it's not a client, so, no need to request if (jid != name) { const Shared::ClientId& id = data["client"].value(); if (!id.valid()) return; if (!clientCache.checkClient(id)) acc->discoverInfo(jid + "/" + name, id.getId()); } } void Core::Squawk::onAccountInfoDiscovered( const QString& address, const QString& node, const std::set& identities, const std::set& features) { Account* acc = static_cast(sender()); if (!clientCache.registerClientInfo(address, node, identities, features)) { qDebug() << "Account" << acc->getName() << "received an ill-formed client discovery response from" << address << "about" << node; } } void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name) { Account* acc = static_cast(sender()); emit removePresence(acc->getName(), jid, name); } void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state) { Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}}); } void Core::Squawk::onAccountChanged(const QMap& data) { Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), data); } void Core::Squawk::onAccountMessage(const Shared::Message& data) { Account* acc = static_cast(sender()); emit accountMessage(acc->getName(), data); } void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to send a message with non existing account" << account << ", skipping"; return; } itr->second->sendMessage(data); } void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping"; return; } itr->second->replaceMessage(originalId, data); } void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping"; return; } itr->second->resendMessage(jid, id); } void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to request an archive of non existing account, skipping"); return; } itr->second->requestArchive(jid, count, before); } void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list& list, bool last) { Account* acc = static_cast(sender()); emit responseArchive(acc->getName(), jid, list, last); } void Core::Squawk::modifyAccountRequest(const QString& name, const QMap& map) { AccountsMap::const_iterator itr = amap.find(name); if (itr == amap.end()) { qDebug("An attempt to modify non existing account, skipping"); return; } Core::Account* acc = itr->second; Shared::ConnectionState st = acc->getState(); QMap::const_iterator mItr; bool needToReconnect = false; bool wentReconnecting = false; mItr = map.find("login"); if (mItr != map.end()) { needToReconnect = acc->getLogin() != mItr->toString(); } if (!needToReconnect) { mItr = map.find("password"); if (mItr != map.end()) { needToReconnect = acc->getPassword() != mItr->toString(); } } if (!needToReconnect) { mItr = map.find("server"); if (mItr != map.end()) { needToReconnect = acc->getServer() != mItr->toString(); } } if (!needToReconnect) { mItr = map.find("resource"); if (mItr != map.end()) { needToReconnect = acc->getResource() != mItr->toString(); } } bool activeChanged = false; mItr = map.find("active"); if (mItr == map.end() || mItr->toBool() == acc->getActive()) { if (needToReconnect && st != Shared::ConnectionState::disconnected) { acc->reconnect(); wentReconnecting = true; } } else { acc->setActive(mItr->toBool()); activeChanged = true; } mItr = map.find("login"); if (mItr != map.end()) acc->setLogin(mItr->toString()); mItr = map.find("password"); if (mItr != map.end()) acc->setPassword(mItr->toString()); mItr = map.find("resource"); if (mItr != map.end()) acc->setResource(mItr->toString()); mItr = map.find("server"); if (mItr != map.end()) acc->setServer(mItr->toString()); mItr = map.find("passwordType"); if (mItr != map.end()) acc->setPasswordType(Shared::Global::fromInt(mItr->toInt())); #ifdef WITH_KWALLET if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success && !needToReconnect ) { kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); } #endif if (state != Shared::Availability::offline) { if (activeChanged && acc->getActive()) acc->connect(); else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) acc->connect(); } emit changeAccount(name, map); } void Core::Squawk::onAccountError(const QString& text) { Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"error", text}}); if (acc->getLastError() == Account::Error::authentication) emit requestPassword(acc->getName(), true); } void Core::Squawk::removeAccountRequest(const QString& name) { AccountsMap::const_iterator itr = amap.find(name); if (itr == amap.end()) { qDebug() << "An attempt to remove non existing account " << name << " from core, skipping"; return; } Account* acc = itr->second; if (acc->getState() != Shared::ConnectionState::disconnected) acc->disconnect(); for (Accounts::const_iterator aItr = accounts.begin(); aItr != accounts.end(); ++aItr) { if (*aItr == acc) { accounts.erase(aItr); break; } } amap.erase(itr); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + name; QDir dir(path); dir.removeRecursively(); emit removeAccount(name); acc->deleteLater(); } void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to subscribe to the contact with non existing account, skipping"); return; } itr->second->subscribeToContact(jid, reason); } void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid, const QString& reason) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to subscribe to the contact with non existing account, skipping"); return; } itr->second->unsubscribeFromContact(jid, reason); } void Core::Squawk::removeContactRequest(const QString& account, const QString& jid) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to remove contact from non existing account, skipping"); return; } itr->second->removeContactRequest(jid); } void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to add contact to a non existing account, skipping"); return; } itr->second->addContactRequest(jid, name, groups); } void Core::Squawk::onAccountAddRoom(const QString jid, const QMap& data) { Account* acc = static_cast(sender()); emit addRoom(acc->getName(), jid, data); } void Core::Squawk::onAccountChangeRoom(const QString jid, const QMap& data) { Account* acc = static_cast(sender()); emit changeRoom(acc->getName(), jid, data); } void Core::Squawk::onAccountRemoveRoom(const QString jid) { Account* acc = static_cast(sender()); emit removeRoom(acc->getName(), jid); } void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, bool joined) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to set jouned to the room" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->setRoomJoined(jid, joined); } void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, bool joined) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to set autoJoin to the room" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->setRoomAutoJoin(jid, joined); } void Core::Squawk::setContactEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to set encryption to the contact" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->setContactEncryption(jid, value); } void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap& data) { Account* acc = static_cast(sender()); emit addRoomParticipant(acc->getName(), jid, nick, data); } void Core::Squawk::onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap& data) { Account* acc = static_cast(sender()); emit changeRoomParticipant(acc->getName(), jid, nick, data); } void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString& nick) { Account* acc = static_cast(sender()); emit removeRoomParticipant(acc->getName(), jid, nick); } void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data) { Account* acc = static_cast(sender()); emit changeMessage(acc->getName(), jid, id, data); } void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to remove the room" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->removeRoomRequest(jid); } void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to add the room" << jid << "to non existing account" << account << ", skipping"; return; } itr->second->addRoomRequest(jid, nick, password, autoJoin); } void Core::Squawk::fileDownloadRequest(const QString& url) { network.downladFile(url); } void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; return; } itr->second->addContactToGroupRequest(jid, groupName); } void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; return; } itr->second->removeContactFromGroupRequest(jid, groupName); } void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->renameContactRequest(jid, newName); } void Core::Squawk::requestInfo(const QString& account, const QString& jid) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to request info about" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->requestInfo(jid); } void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to update info to non existing account" << account << ", skipping"; return; } itr->second->updateInfo(info); } void Core::Squawk::readSettings() { QSettings settings; settings.beginGroup("core"); int size = settings.beginReadArray("accounts"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); Shared::AccountPassword passwordType = Shared::Global::fromInt( settings.value("passwordType", static_cast(Shared::AccountPassword::plain)).toInt() ); QString name = settings.value("name").toString(); QString password = settings.value("password", "").toString(); if (passwordType == Shared::AccountPassword::jammed) { #ifdef WITH_SIMPLE_CRYPT SimpleCrypt crypto(passwordHash); password = crypto.decryptToString(password); #else qDebug() << "The password for account" << name << "is jammed, but Squawk was compiled without SimpleCrypt support"; qDebug("Can not decode password, setting this account to always ask password mode"); passwordType = Shared::AccountPassword::alwaysAsk; #endif } addAccount( settings.value("login").toString(), settings.value("server").toString(), password, name, settings.value("resource").toString(), settings.value("active").toBool(), passwordType ); } settings.endArray(); settings.endGroup(); qDebug() << "Squawk core is ready"; emit ready(); } void Core::Squawk::onAccountNeedPassword() { Account* acc = static_cast(sender()); switch (acc->getPasswordType()) { case Shared::AccountPassword::alwaysAsk: emit requestPassword(acc->getName(), false); break; case Shared::AccountPassword::kwallet: { #ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { kwallet.requestReadPassword(acc->getName()); } else { #endif emit requestPassword(acc->getName(), false); #ifdef WITH_KWALLET } #endif break; } default: break; //should never happen; } } void Core::Squawk::onWalletRejectPassword(const QString& login) { emit requestPassword(login, false); } void Core::Squawk::responsePassword(const QString& account, const QString& password) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; return; } Account* acc = itr->second; acc->setPassword(password); emit changeAccount(account, {{"password", password}}); if (state != Shared::Availability::offline && acc->getActive()) acc->connect(); } void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText) { Account* acc = static_cast(sender()); emit fileError({{acc->getName(), jid, id}}, errorText, true); } void Core::Squawk::onLocalPathInvalid(const QString& path) { std::list list = network.reportPathInvalid(path); QMap data({{"attachPath", ""}}); for (const Shared::MessageInfo& info : list) { AccountsMap::const_iterator itr = amap.find(info.account); if (itr != amap.end()) { itr->second->requestChangeMessage(info.jid, info.messageId, data); } else { qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping"; } } } void Core::Squawk::changeDownloadsPath(const QString& path) { network.moveFilesDirectory(path); }