diff --git a/qtox.pro b/qtox.pro index 215237c00..47b687e83 100644 --- a/qtox.pro +++ b/qtox.pro @@ -32,7 +32,6 @@ FORMS += \ src/widget/form/settings/identitysettings.ui \ src/widget/form/settings/privacysettings.ui \ src/widget/form/loadhistorydialog.ui \ - src/widget/form/inputpassworddialog.ui \ src/widget/form/setpassworddialog.ui \ src/widget/form/settings/advancedsettings.ui @@ -99,7 +98,7 @@ win32 { target.path = /usr/bin INSTALLS += target LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic - LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig + LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 } else { LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc @@ -176,7 +175,6 @@ HEADERS += src/widget/form/addfriendform.h \ src/misc/db/genericddinterface.h \ src/misc/db/plaindb.h \ src/misc/db/encrypteddb.h \ - src/widget/form/inputpassworddialog.h \ src/widget/form/setpassworddialog.h \ src/widget/form/tabcompleter.h \ src/video/videoframe.h \ @@ -207,6 +205,7 @@ SOURCES += \ src/widget/groupwidget.cpp \ src/widget/widget.cpp \ src/core.cpp \ + src/coreencryption.cpp \ src/friend.cpp \ src/friendlist.cpp \ src/group.cpp \ @@ -243,7 +242,6 @@ SOURCES += \ src/misc/db/genericddinterface.cpp \ src/misc/db/plaindb.cpp \ src/misc/db/encrypteddb.cpp \ - src/widget/form/inputpassworddialog.cpp \ src/widget/form/setpassworddialog.cpp \ src/video/netvideosource.cpp \ src/widget/form/tabcompleter.cpp \ diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 604442749..9c4fcc666 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -490,8 +490,8 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker() QDir updateDir(updateDirStr); if ((updateDir.exists() && QFile(updateDirStr+"flist").exists()) - || Widget::getInstance()->askMsgboxQuestion(QObject::tr("Update", "The title of a message box"), - QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."))) + || Widget::getInstance()->askQuestion(QObject::tr("Update", "The title of a message box"), + QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."), true, false)) { downloadUpdate(); } diff --git a/src/core.cpp b/src/core.cpp index 0b5ff1f74..9cc9978de 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -23,7 +23,6 @@ #include "src/audio.h" #include -#include #include #include @@ -39,7 +38,6 @@ #include #include #include -#include #include const QString Core::CONFIG_FILE_NAME = "data"; @@ -90,13 +88,24 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : Audio::openInput(inDevDescr); } +void Core::deadifyTox() +{ + if (toxav) + { + toxav_kill(toxav); + toxav = nullptr; + } + if (tox) + { + tox_kill(tox); + tox = nullptr; + } +} + Core::~Core() { qDebug() << "Deleting Core"; - clearPassword(Core::ptMain); - clearPassword(Core::ptHistory); - toxTimer->stop(); coreThread->exit(0); while (coreThread->isRunning()) @@ -105,13 +114,7 @@ Core::~Core() coreThread->wait(500); } - if (tox) - { - toxav_kill(toxav); - toxav = nullptr; - tox_kill(tox); - tox = nullptr; - } + deadifyTox(); if (videobuf) { @@ -183,12 +186,9 @@ void Core::make_tox() { if (toxOptions.proxy_type != TOX_PROXY_NONE) { - //QMessageBox::critical(Widget::getInstance(), tr("Proxy failure", "popup title"), - //tr("toxcore failed to start with your proxy settings. qTox cannot run; please modify your " - //"settings and restart.", "popup text")); qCritical() << "Core: bad proxy! no toxcore!"; emit badProxy(); - } + } else { qCritical() << "Tox core failed to start"; @@ -229,14 +229,26 @@ void Core::start() if (loadPath != "") { - if (!loadConfiguration(loadPath)) // loadPath is meaningless after this - { - qCritical() << "Core: loadConfiguration failed, exiting now"; - emit failedToStart(); - tox_kill(tox); - tox = nullptr; - return; + while (!loadConfiguration(loadPath)) + { + if (loadPath.isEmpty()) + { + QString profile; + if ((profile = loadOldInformation()).isEmpty()) + { + qCritical() << "Core: loadConfiguration failed, exiting now"; + emit failedToStart(); + return; + } + else + { + loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); + Settings::getInstance().switchProfile(profile); + HistoryKeeper::resetInstance(); // I'm not actually sure if this is necessary + } + } } + // loadPath is meaningless after this loadPath = ""; } else // new ID @@ -292,7 +304,7 @@ void Core::start() } else qDebug() << "Core: Error loading self avatar"; - + ready = true; process(); // starts its own timer @@ -309,7 +321,7 @@ void Core::start() void Core::process() { - if (!tox) + if (!isReady()) return; static int tolerance = CORE_DISCONNECT_TOLERANCE; @@ -501,7 +513,6 @@ void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *da void Core::onGroupMessage(Tox*, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *_core) { Core* core = static_cast(_core); - emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false); } @@ -1012,7 +1023,7 @@ void Core::acceptFileRecvRequest(int friendId, int fileNum, QString path) void Core::removeFriend(int friendId, bool fake) { - if (!tox || fake) + if (!isReady() || fake) return; if (tox_del_friend(tox, friendId) == -1) { emit failedToRemoveFriend(friendId); @@ -1024,7 +1035,7 @@ void Core::removeFriend(int friendId, bool fake) void Core::removeGroup(int groupId, bool fake) { - if (!tox || fake) + if (!isReady() || fake) return; tox_del_groupchat(tox, groupId); @@ -1162,7 +1173,8 @@ QString Core::sanitize(QString name) bool Core::loadConfiguration(QString path) { - // setting the profile is now the responsibility of the caller + loadPath = ""; // if not empty upon return, then user forgot a password and is switching + QFile configurationFile(path); qDebug() << "Core::loadConfiguration: reading from " << path; @@ -1186,51 +1198,21 @@ bool Core::loadConfiguration(QString path) } else if (error == 1) // Encrypted data save { - if (!Settings::getInstance().getEncryptTox()) - Widget::getInstance()->showWarningMsgBox(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); - uint8_t salt[tox_pass_salt_length()]; - tox_get_salt(reinterpret_cast(data.data()), salt); - do + if (!loadEncryptedSave(data)) { - while (!pwsaltedkeys[ptMain]) + configurationFile.close(); + + QString profile; + QMetaObject::invokeMethod(Widget::getInstance(), "askProfiles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); + + if (!profile.isEmpty()) { - emit blockingGetPassword(tr("Tox datafile decryption password"), ptMain, salt); - if (!pwsaltedkeys[ptMain]) - Widget::getInstance()->showWarningMsgBox(tr("Password error"), tr("Failed to setup password.\nEmpty password.")); + loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); + Settings::getInstance().switchProfile(profile); + HistoryKeeper::resetInstance(); } - - error = tox_encrypted_key_load(tox, reinterpret_cast(data.data()), data.size(), pwsaltedkeys[ptMain]); - if (error != 0) - { - QMessageBox msgb; - msgb.moveToThread(qApp->thread()); - QPushButton *tryAgain = msgb.addButton(tr("Try Again"), QMessageBox::AcceptRole); - QPushButton *cancel = msgb.addButton(tr("Change profile"), QMessageBox::RejectRole); - QPushButton *wipe = msgb.addButton(tr("Reinit current profile"), QMessageBox::ActionRole); - msgb.setDefaultButton(tryAgain); - msgb.setWindowTitle(tr("Password error")); - msgb.setText(tr("Wrong password has been entered")); - // msgb.setInformativeText(tr("")); - - msgb.exec(); - - if (msgb.clickedButton() == tryAgain) - clearPassword(ptMain); - else if (msgb.clickedButton() == cancel) - { - configurationFile.close(); - return false; - } - else if (msgb.clickedButton() == wipe) - { - clearPassword(ptMain); - Settings::getInstance().setEncryptTox(false); - error = 0; - } - } - else - Settings::getInstance().setEncryptTox(true); - } while (error != 0); + return false; + } } } configurationFile.close(); @@ -1239,7 +1221,7 @@ bool Core::loadConfiguration(QString path) QString name = getUsername(); if (!name.isEmpty()) emit usernameSet(name); - + QString msg = getStatusMessage(); if (!msg.isEmpty()) emit statusMessageSet(msg); @@ -1250,54 +1232,7 @@ bool Core::loadConfiguration(QString path) // tox core is already decrypted if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) - { - bool error = true; - - // get salt - QFile file(HistoryKeeper::getHistoryPath()); - file.open(QIODevice::ReadOnly); - QByteArray data = file.read(tox_pass_encryption_extra_length()); - file.close(); - uint8_t salt[tox_pass_salt_length()]; - int err = tox_get_salt(reinterpret_cast(data.data()), salt); - if (err) - { // maybe we should handle this better - qWarning() << "Core: history db isn't encrypted, but encryption is set!! No history loaded..."; - } - else - { - do - { - while (!pwsaltedkeys[ptHistory]) - { - emit blockingGetPassword(tr("History Log decryption password"), Core::ptHistory, salt); - if (!pwsaltedkeys[ptHistory]) - Widget::getInstance()->showWarningMsgBox(tr("Password error"), tr("Failed to setup password.\nEmpty password.")); - } - - if (!HistoryKeeper::checkPassword()) - { - if (QMessageBox::Ok == Widget::getInstance()->showWarningMsgBox(tr("Encrypted log"), - tr("Your history is encrypted with different password.\nDo you want to try another password?"), - QMessageBox::Ok | QMessageBox::Cancel)) - { - error = true; - clearPassword(ptHistory); - } - else - { - error = false; - clearPassword(ptHistory); - Widget::getInstance()->showWarningMsgBox(tr("History"), tr("Due to incorret password history will be disabled.")); - Settings::getInstance().setEncryptLogs(false); - Settings::getInstance().setEnableLogging(false); - } - } else { - error = false; - } - } while (error); - } - } + checkEncryptedHistory(); loadFriends(); return true; @@ -1308,13 +1243,16 @@ void Core::saveConfiguration() if (QThread::currentThread() != coreThread) return (void) QMetaObject::invokeMethod(this, "saveConfiguration"); + if (!isReady()) + return; + QString dir = Settings::getSettingsDirPath(); QDir directory(dir); if (!directory.exists() && !directory.mkpath(directory.absolutePath())) { qCritical() << "Error while creating directory " << dir; return; } - + QString profile = Settings::getInstance().getCurrentProfile(); if (profile == "") @@ -1324,74 +1262,14 @@ void Core::saveConfiguration() if (profile == "") // happens on creation of a new Tox ID profile = getIDString(); - Settings::getInstance().setCurrentProfile(profile); + Settings::getInstance().switchProfile(profile); } - + QString path = directory.filePath(profile + TOX_EXT); - + saveConfiguration(path); } -void Core::saveConfiguration(const QString& path) -{ - if (QThread::currentThread() != coreThread) - return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path)); - - if (!tox) - { - qWarning() << "Core::saveConfiguration: Tox not started, aborting!"; - return; - } - - Settings::getInstance().save(); - - QSaveFile configurationFile(path); - if (!configurationFile.open(QIODevice::WriteOnly)) - { - qCritical() << "File " << path << " cannot be opened"; - return; - } - - qDebug() << "Core: writing tox_save to " << path; - - uint32_t fileSize; - bool encrypt = Settings::getInstance().getEncryptTox(); - if (encrypt) - fileSize = tox_encrypted_size(tox); - else - fileSize = tox_size(tox); - - if (fileSize > 0 && fileSize <= INT32_MAX) - { - uint8_t *data = new uint8_t[fileSize]; - if (encrypt) - { - if (!pwsaltedkeys[ptMain]) - { - Widget::getInstance()->showWarningMsgBox(tr("NO Password"), tr("Will be saved without encryption!")); - tox_save(tox, data); - } - else - { - int ret = tox_encrypted_key_save(tox, data, pwsaltedkeys[ptMain]); - if (ret == -1) - { - qCritical() << "Core::saveConfiguration: encryption of save file failed!!!"; - return; - } - } - } - else - { - tox_save(tox, data); - } - - configurationFile.write(reinterpret_cast(data), fileSize); - configurationFile.commit(); - delete[] data; - } -} - void Core::switchConfiguration(const QString& profile) { if (profile.isEmpty()) @@ -1400,19 +1278,15 @@ void Core::switchConfiguration(const QString& profile) qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile; saveConfiguration(); + saveCurrentInformation(); // part of a hack, see core.h ready = false; + Widget::getInstance()->setEnabledThreadsafe(false); clearPassword(ptMain); clearPassword(ptHistory); toxTimer->stop(); - Widget::getInstance()->setEnabledThreadsafe(false); - if (tox) { - toxav_kill(toxav); - toxav = nullptr; - tox_kill(tox); - tox = nullptr; - } + deadifyTox(); emit selfAvatarChanged(QPixmap(":/img/contact_dark.png")); emit blockingClearContacts(); // we need this to block, but signals are required for thread safety @@ -1422,15 +1296,12 @@ void Core::switchConfiguration(const QString& profile) else loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - // the new profile needs to be set before resetting the settings, so that - // we don't load the old profile's profile.ini - Settings::getInstance().setCurrentProfile(profile); - Settings::getInstance().save(false); // save new profile, but don't write old profile info to newprofile.ini - Settings::resetInstance(); - HistoryKeeper::getInstance()->resetInstance(); + Settings::getInstance().switchProfile(profile); + HistoryKeeper::resetInstance(); start(); - Widget::getInstance()->setEnabledThreadsafe(true); + if (isReady()) + Widget::getInstance()->setEnabledThreadsafe(true); } void Core::loadFriends() @@ -1795,69 +1666,6 @@ QList Core::splitMessage(const QString &message, int maxLen) return splittedMsgs; } -void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) -{ - if (password.isEmpty()) - { - clearPassword(passtype); - return; - } - if (!pwsaltedkeys[passtype]) - pwsaltedkeys[passtype] = new uint8_t[tox_pass_key_length()]; - - CString str(password); - if (salt) - tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype]); - else - tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype]); - - password.clear(); -} - -void Core::clearPassword(PasswordType passtype) -{ - if (pwsaltedkeys[passtype]) - { - delete[] pwsaltedkeys[passtype]; - pwsaltedkeys[passtype] = nullptr; - } -} - -QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) -{ - if (!pwsaltedkeys[passtype]) - return QByteArray(); - uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()]; - if (tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1) - { - qWarning() << "Core::encryptData: encryption failed"; - return QByteArray(); - } - return QByteArray(reinterpret_cast(encrypted), data.size() + tox_pass_encryption_extra_length()); -} - -QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) -{ - if (!pwsaltedkeys[passtype]) - return QByteArray(); - int sz = data.size() - tox_pass_encryption_extra_length(); - uint8_t decrypted[sz]; - if (tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], decrypted) != sz) - { - qWarning() << "Core::decryptData: decryption failed"; - return QByteArray(); - } - return QByteArray(reinterpret_cast(decrypted), sz); -} - -bool Core::isPasswordSet(PasswordType passtype) -{ - if (pwsaltedkeys[passtype]) - return true; - - return false; -} - QString Core::getPeerName(const ToxID& id) const { QString name; @@ -1892,7 +1700,7 @@ QString Core::getPeerName(const ToxID& id) const bool Core::isReady() { - return ready; + return toxav && tox && ready; } void Core::setNospam(uint32_t nospam) diff --git a/src/core.h b/src/core.h index de9b62215..4257eb80e 100644 --- a/src/core.h +++ b/src/core.h @@ -46,12 +46,14 @@ public: explicit Core(Camera* cam, QThread* coreThread, QString initialLoadPath); static Core* getInstance(); ///< Returns the global widget's Core instance ~Core(); - + static const QString TOX_EXT; static const QString CONFIG_FILE_NAME; static QString sanitize(QString name); static QList splitMessage(const QString &message, int maxLen); + static QByteArray getSaltFromFile(QString filename); + QString getPeerName(const ToxID& id) const; int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure @@ -64,9 +66,9 @@ public: bool hasFriendWithPublicKey(const QString &pubkey) const; ///< Check if we have a friend by public key int joinGroupchat(int32_t friendNumber, uint8_t type, const uint8_t* pubkey,uint16_t length) const; ///< Accept a groupchat invite void quitGroupChat(int groupId) const; ///< Quit a groupchat - + QString getIDString() const; ///< Get the 12 first characters of our Tox ID - + QString getUsername() const; ///< Returns our username, or an empty string on failure QString getStatusMessage() const; ///< Returns our status message, or an empty string on failure ToxID getSelfId() const; ///< Returns our Tox ID @@ -83,10 +85,10 @@ public slots: void start(); ///< Initializes the core, must be called before anything else void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer void bootstrapDht(); ///< Connects us to the Tox network - void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core void saveConfiguration(); void saveConfiguration(const QString& path); + void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core void acceptFriendRequest(const QString& userId); void requestFriendship(const QString& friendAddress, const QString& message); @@ -137,6 +139,7 @@ public slots: static bool isGroupCallVolEnabled(int groupId); void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); + void useOtherPassword(PasswordType type); void clearPassword(PasswordType passtype); QByteArray encryptData(const QByteArray& data, PasswordType passtype); QByteArray decryptData(const QByteArray& data, PasswordType passtype); @@ -145,7 +148,6 @@ signals: void connected(); void disconnected(); void blockingClearContacts(); - void blockingGetPassword(QString info, int passtype, uint8_t* salt = nullptr); void friendRequestReceived(const QString& userId, const QString& message); void friendMessageReceived(int friendId, const QString& message, bool isAction); @@ -266,6 +268,8 @@ private: bool checkConnection(); bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise + bool loadEncryptedSave(QByteArray& data); + void checkEncryptedHistory(); void make_tox(); void loadFriends(); @@ -274,6 +278,8 @@ private: void checkLastOnline(int friendId); + void deadifyTox(); + private slots: void onFileTransferFinished(ToxFile file); @@ -294,7 +300,17 @@ private: QMutex fileSendMutex, messageSendMutex; bool ready; - uint8_t* pwsaltedkeys[PasswordType::ptCounter]; // use the pw's hash as the "pw" + uint8_t* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" + + // Hack for reloading current profile if switching to an encrypted one fails. + // Testing the passwords before killing the current profile is perfectly doable, + // however it would require major refactoring; + // the Core class as a whole also requires major refactoring (especially to support multiple IDs at once), + // so I'm punting on this until then, when it would get fixed anyways + uint8_t* backupkeys[PasswordType::ptCounter] = {nullptr}; + QString* backupProfile = nullptr; + void saveCurrentInformation(); + QString loadOldInformation(); static const int videobufsize; static uint8_t* videobuf; diff --git a/src/coreencryption.cpp b/src/coreencryption.cpp new file mode 100644 index 000000000..9821cac03 --- /dev/null +++ b/src/coreencryption.cpp @@ -0,0 +1,327 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre 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 COPYING file for more details. +*/ + +/* This file is part of the Core class, but was separated for my sanity */ +/* The load function delegates to loadEncrypted here, and the save function */ +/* was permanently moved here to handle encryption */ + +#include "core.h" +#include "src/widget/widget.h" +#include +#include +#include "src/misc/settings.h" +#include "misc/cstring.h" +#include "historykeeper.h" +#include + +#include +#include +#include +#include + +void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) +{ + clearPassword(passtype); + if (password.isEmpty()) + return; + + pwsaltedkeys[passtype] = new uint8_t[tox_pass_key_length()]; + + CString str(password); + if (salt) + tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype]); + else + tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype]); + + password.clear(); +} + +#include +void Core::useOtherPassword(PasswordType type) +{ + clearPassword(type); + pwsaltedkeys[type] = new uint8_t[tox_pass_key_length()]; + + PasswordType other = (type == ptMain) ? ptHistory : ptMain; + + std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+tox_pass_key_length(), pwsaltedkeys[type]); +} + +void Core::clearPassword(PasswordType passtype) +{ + delete[] pwsaltedkeys[passtype]; + pwsaltedkeys[passtype] = nullptr; +} + +// part of a hack, see core.h +void Core::saveCurrentInformation() +{ + if (pwsaltedkeys[ptMain]) + { + backupkeys[ptMain] = new uint8_t[tox_pass_key_length()]; + std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+tox_pass_key_length(), backupkeys[ptMain]); + } + if (pwsaltedkeys[ptHistory]) + { + backupkeys[ptHistory] = new uint8_t[tox_pass_key_length()]; + std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+tox_pass_key_length(), backupkeys[ptHistory]); + } + backupProfile = new QString(Settings::getInstance().getCurrentProfile()); +} + +QString Core::loadOldInformation() +{ + QString out; + if (backupProfile) + { + out = *backupProfile; + delete backupProfile; + backupProfile = nullptr; + } + backupProfile = nullptr; + clearPassword(ptMain); + clearPassword(ptHistory); + // we can just copy the pointer, as long as we null out backupkeys + // (if backupkeys was null anyways, then this is a null-op) + pwsaltedkeys[ptMain] = backupkeys[ptMain]; + pwsaltedkeys[ptHistory] = backupkeys[ptHistory]; + backupkeys[ptMain] = nullptr; + backupkeys[ptHistory] = nullptr; + return out; +} + +QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) +{ + if (!pwsaltedkeys[passtype]) + return QByteArray(); + uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()]; + if (tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1) + { + qWarning() << "Core::encryptData: encryption failed"; + return QByteArray(); + } + return QByteArray(reinterpret_cast(encrypted), data.size() + tox_pass_encryption_extra_length()); +} + +QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) +{ + if (!pwsaltedkeys[passtype]) + return QByteArray(); + int sz = data.size() - tox_pass_encryption_extra_length(); + uint8_t decrypted[sz]; + int decr_size = tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], decrypted); + if (decr_size != sz) + { + qWarning() << "Core::decryptData: decryption failed"; + return QByteArray(); + } + return QByteArray(reinterpret_cast(decrypted), sz); +} + +bool Core::isPasswordSet(PasswordType passtype) +{ + if (pwsaltedkeys[passtype]) + return true; + + return false; +} + +QByteArray Core::getSaltFromFile(QString filename) +{ + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Core: file" << filename << "doesn't exist"; + return QByteArray(); + } + QByteArray data = file.read(tox_pass_encryption_extra_length()); + file.close(); + + uint8_t *salt = new uint8_t[tox_pass_salt_length()]; + int err = tox_get_salt(reinterpret_cast(data.data()), salt); + if (err) + { + qWarning() << "Core: can't get salt from" << filename << "header"; + return QByteArray(); + } + + QByteArray res = QByteArray::fromRawData(reinterpret_cast(salt), tox_pass_salt_length()); + return res; +} + +bool Core::loadEncryptedSave(QByteArray& data) +{ + if (!Settings::getInstance().getEncryptTox()) + Widget::getInstance()->showWarningMsgBox(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); + + int error = -1; + QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile())); + QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); + QString dialogtxt; + + if (pwsaltedkeys[ptMain]) // password set, try it + { + error = tox_encrypted_key_load(tox, reinterpret_cast(data.data()), data.size(), pwsaltedkeys[ptMain]); + if (!error) + { + Settings::getInstance().setEncryptTox(true); + return true; + } + dialogtxt = tr("The stored profile password failed. Please try another:", "used only when pw set before load() doesn't work"); + } + else + dialogtxt = a; + + uint8_t salt[tox_pass_salt_length()]; + tox_get_salt(reinterpret_cast(data.data()), salt); + + do + { + QString pw = Widget::getInstance()->passwordDialog(tr("Change profile"), dialogtxt); + + if (pw.isEmpty()) + { + clearPassword(ptMain); + return false; + } + else + setPassword(pw, ptMain, salt); + + error = tox_encrypted_key_load(tox, reinterpret_cast(data.data()), data.size(), pwsaltedkeys[ptMain]); + dialogtxt = a + "\n" + b; + } while (error != 0); + + Settings::getInstance().setEncryptTox(true); + return true; +} + +void Core::checkEncryptedHistory() +{ + QString path = HistoryKeeper::getHistoryPath(); + bool exists = QFile::exists(path); + + QByteArray salt = getSaltFromFile(path); + if (exists && salt.size() == 0) + { // maybe we should handle this better + Widget::getInstance()->showWarningMsgBox(tr("Encrypted History"), tr("No encrypted history file found, or it was corrupted.\nHistory will be disabled!")); + Settings::getInstance().setEncryptLogs(false); + Settings::getInstance().setEnableLogging(false); + HistoryKeeper::resetInstance(); + return; + } + + QString a(tr("Please enter the password for the chat logs for the %1 profile.", "used in load() when no hist pw set").arg(Settings::getInstance().getCurrentProfile())); + QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); + QString dialogtxt; + + if (pwsaltedkeys[ptHistory]) + { + if (!exists || HistoryKeeper::checkPassword()) + return; + dialogtxt = tr("The stored chat log password failed. Please try another:", "used only when pw set before load() doesn't work"); + } + else + dialogtxt = a; + + if (pwsaltedkeys[ptMain]) + { + useOtherPassword(ptHistory); + if (!exists || HistoryKeeper::checkPassword()) + { + qDebug() << "Core: using main password for history"; + return; + } + clearPassword(ptHistory); + } + + bool error = true; + do + { + QString pw = Widget::getInstance()->passwordDialog(tr("Disable history"), dialogtxt); + + if (pw.isEmpty()) + { + clearPassword(ptHistory); + Settings::getInstance().setEncryptLogs(false); + Settings::getInstance().setEnableLogging(false); + HistoryKeeper::resetInstance(); + return; + } + else + setPassword(pw, ptHistory, reinterpret_cast(salt.data())); + + error = exists && !HistoryKeeper::checkPassword(); + dialogtxt = a + "\n" + b; + } while (error); +} + +void Core::saveConfiguration(const QString& path) +{ + if (QThread::currentThread() != coreThread) + return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path)); + + if (!isReady()) + { + qWarning() << "Core::saveConfiguration: Tox not started, aborting!"; + return; + } + + QSaveFile configurationFile(path); + if (!configurationFile.open(QIODevice::WriteOnly)) { + qCritical() << "File " << path << " cannot be opened"; + return; + } + + qDebug() << "Core: writing tox_save to " << path; + + uint32_t fileSize; bool encrypt = Settings::getInstance().getEncryptTox(); + if (encrypt) + fileSize = tox_encrypted_size(tox); + else + fileSize = tox_size(tox); + + if (fileSize > 0 && fileSize <= INT32_MAX) { + uint8_t *data = new uint8_t[fileSize]; + + if (encrypt) + { + if (!pwsaltedkeys[ptMain]) + { + // probably zero chance event + Widget::getInstance()->showWarningMsgBox(tr("NO Password"), tr("Encryption is enabled, but there is no password! Encryption will be disabled.")); + Settings::getInstance().setEncryptTox(false); + tox_save(tox, data); + } + else + { + int ret = tox_encrypted_key_save(tox, data, pwsaltedkeys[ptMain]); + if (ret == -1) + { + qCritical() << "Core::saveConfiguration: encryption of save file failed!!!"; + return; + } + } + } + else + tox_save(tox, data); + + configurationFile.write(reinterpret_cast(data), fileSize); + configurationFile.commit(); + delete[] data; + } + + Settings::getInstance().save(); +} diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index a923f53c5..d72f6a09d 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -68,20 +68,15 @@ HistoryKeeper *HistoryKeeper::getInstance() return historyInstance; } -bool HistoryKeeper::checkPassword() +bool HistoryKeeper::checkPassword(int encrypted) { - if (Settings::getInstance().getEnableLogging()) - { - if (Settings::getInstance().getEncryptLogs()) - { - QString dbpath = getHistoryPath(); - return EncryptedDb::check(dbpath); - } else { - return true; - } - } else { + if (!Settings::getInstance().getEnableLogging() && (encrypted == -1)) return true; - } + + if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) + return EncryptedDb::check(getHistoryPath(Settings::getInstance().getCurrentProfile(), encrypted)); + + return true; } HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : @@ -132,9 +127,10 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : setSyncType(Settings::getInstance().getDbSyncType()); + messageID = 0; QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";"); - sqlAnswer.first(); - messageID = sqlAnswer.value(0).toInt(); + if (sqlAnswer.first()) + messageID = sqlAnswer.value(0).toLongLong(); } HistoryKeeper::~HistoryKeeper() @@ -142,16 +138,13 @@ HistoryKeeper::~HistoryKeeper() delete db; } -int HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent) +qint64 HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent) { - int chat_id = getChatID(chat, ctSingle).first; - int sender_id = getAliasID(sender); + QList cmds = generateAddChatEntryCmd(chat, message, sender, dt, isSent); db->exec("BEGIN TRANSACTION;"); - db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message) ") + - QString("VALUES (%1, %2, %3, '%4');") - .arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message))); - db->exec(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent)); + for (auto &it : cmds) + db->exec(it); db->exec("COMMIT TRANSACTION;"); messageID++; @@ -190,12 +183,66 @@ QList HistoryKeeper::getChatHistory(HistoryKeeper::C QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt); - res.push_back({id, sender,message,time,isSent}); + res.push_back(HistMessage(id, "", sender, message, time, isSent)); } return res; } +QList HistoryKeeper::exportMessages() +{ + QSqlQuery dbAnswer; + dbAnswer = db->exec(QString("SELECT history.id, timestamp, user_id, message, status, name FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + + QString("INNER JOIN aliases ON history.sender = aliases.id INNER JOIN chats ON history.chat_id = chats.id;")); + + QList res; + + while (dbAnswer.next()) + { + qint64 id = dbAnswer.value(0).toLongLong(); + qint64 timeInt = dbAnswer.value(1).toLongLong(); + QString sender = dbAnswer.value(2).toString(); + QString message = unWrapMessage(dbAnswer.value(3).toString()); + bool isSent = true; + if (!dbAnswer.value(4).isNull()) + isSent = dbAnswer.value(4).toBool(); + QString chat = dbAnswer.value(5).toString(); + QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt); + + res.push_back(HistMessage(id, chat, sender, message, time, isSent)); + } + + return res; +} + +void HistoryKeeper::importMessages(const QList &lst) +{ + db->exec("BEGIN TRANSACTION;"); + for (const HistMessage &msg : lst) + { + QList cmds = generateAddChatEntryCmd(msg.chat, msg.message, msg.sender, msg.timestamp, msg.isSent); + for (auto &it : cmds) + db->exec(it); + + messageID++; + } + db->exec("COMMIT TRANSACTION;"); +} + +QList HistoryKeeper::generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent) +{ + QList cmds; + + int chat_id = getChatID(chat, ctSingle).first; + int sender_id = getAliasID(sender); + + cmds.push_back(QString("INSERT INTO history (timestamp, chat_id, sender, message) VALUES (%1, %2, %3, '%4');") + .arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message))); + cmds.push_back(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent)); + + return cmds; +} + QString HistoryKeeper::wrapMessage(const QString &str) { QString wrappedMessage(str); @@ -272,7 +319,7 @@ void HistoryKeeper::resetInstance() historyInstance = nullptr; } -int HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt) +qint64 HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt) { Q_UNUSED(chat) Q_UNUSED(message) @@ -342,3 +389,29 @@ void HistoryKeeper::setSyncType(Db::syncType sType) db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd)); } + +bool HistoryKeeper::isFileExist() +{ + QString path = getHistoryPath(); + QFile file(path); + + return file.exists(); +} + +bool HistoryKeeper::removeHistory(int encrypted) +{ + resetInstance(); + + QString path = getHistoryPath(QString(), encrypted); + QFile DbFile(path); + return DbFile.remove(); +} + +QList HistoryKeeper::exportMessagesDeleteFile(int encrypted) +{ + auto msgs = getInstance()->exportMessages(); + qDebug() << "HistoryKeeper: count" << msgs.size() << "messages exported"; + if (!removeHistory(encrypted)) + qWarning() << "HistoryKeeper: couldn't delete old log file!"; + return msgs; +} diff --git a/src/historykeeper.h b/src/historykeeper.h index 300232e33..ad80b536f 100644 --- a/src/historykeeper.h +++ b/src/historykeeper.h @@ -31,7 +31,11 @@ public: struct HistMessage { + HistMessage(qint64 id, QString chat, QString sender, QString message, QDateTime timestamp, bool isSent) : + id(id), chat(chat), sender(sender), message(message), timestamp(timestamp), isSent(isSent) {} + qint64 id; + QString chat; QString sender; QString message; QDateTime timestamp; @@ -44,14 +48,20 @@ public: static void resetInstance(); static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify - static bool checkPassword(); + static bool checkPassword(int encrypted = -1); + static bool isFileExist(); static void renameHistory(QString from, QString to); + static bool removeHistory(int encrypted = -1); + static QList exportMessagesDeleteFile(int encrypted = -1); - int addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent); - int addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt); + qint64 addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent); + qint64 addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt); QList getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to); void markAsSent(int m_id); + QList exportMessages(); + void importMessages(const QList &lst); + void setSyncType(Db::syncType sType); private: @@ -65,13 +75,14 @@ private: int getAliasID(const QString &id_str); QString wrapMessage(const QString &str); QString unWrapMessage(const QString &str); + QList generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent); ChatType convertToChatType(int); GenericDdInterface *db; QMap aliases; QMap> chats; - int messageID; + qint64 messageID; }; #endif // HISTORYKEEPER_H diff --git a/src/main.cpp b/src/main.cpp index fe01eb096..c05045a33 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox")) { qDebug() << "Setting profile to" << profile; - Settings::getInstance().setCurrentProfile(profile); + Settings::getInstance().switchProfile(profile); } else qWarning() << "Warning: -P profile" << profile + ".tox" << "doesn't exist"; diff --git a/src/misc/db/encrypteddb.cpp b/src/misc/db/encrypteddb.cpp index 56ac73c09..709608667 100644 --- a/src/misc/db/encrypteddb.cpp +++ b/src/misc/db/encrypteddb.cpp @@ -24,28 +24,36 @@ #include #include -qint64 EncryptedDb::plainChunkSize = 4096; -qint64 EncryptedDb::encryptedChunkSize = EncryptedDb::plainChunkSize + tox_pass_encryption_extra_length(); +qint64 EncryptedDb::encryptedChunkSize = 4096; +qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - tox_pass_encryption_extra_length(); EncryptedDb::EncryptedDb(const QString &fname, QList initList) : - PlainDb(":memory:", initList), encrFile(fname) + PlainDb(":memory:", initList), fileName(fname) { QByteArray fileContent; - if (pullFileContent()) + if (pullFileContent(fileName, buffer)) { - chunkPosition = encrFile.size() / encryptedChunkSize; - - encrFile.seek(0); + qDebug() << "EncryptedDb: writing old data"; + encrFile.setFileName(fileName); + encrFile.open(QIODevice::ReadOnly); fileContent = encrFile.readAll(); - } else { - qWarning() << "corrupted history log file will be wiped!"; - chunkPosition = 0; + chunkPosition = encrFile.size() / encryptedChunkSize; + encrFile.close(); } + else + chunkPosition = 0; - encrFile.close(); - encrFile.open(QIODevice::WriteOnly); - encrFile.write(fileContent); - encrFile.flush(); + encrFile.setFileName(fileName); + + if (!encrFile.open(QIODevice::WriteOnly)) + { + qWarning() << "can't open file:" << fileName; + } + else + { + encrFile.write(fileContent); + encrFile.flush(); + } } EncryptedDb::~EncryptedDb() @@ -57,27 +65,34 @@ EncryptedDb::~EncryptedDb() QSqlQuery EncryptedDb::exec(const QString &query) { QSqlQuery retQSqlQuery = PlainDb::exec(query); - if (query.startsWith("INSERT", Qt::CaseInsensitive)) + if (checkCmd(query)) appendToEncrypted(query); return retQSqlQuery; } -bool EncryptedDb::pullFileContent() +bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf) { - encrFile.open(QIODevice::ReadOnly); + QFile dbFile(fname); + if (!dbFile.open(QIODevice::ReadOnly)) + { + qDebug() << "EncryptedDb::pullFileContent: file doesn't exist"; + buf = QByteArray(); + return false; + } QByteArray fileContent; - while (!encrFile.atEnd()) + while (!dbFile.atEnd()) { - QByteArray encrChunk = encrFile.read(encryptedChunkSize); - buffer = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); - if (buffer.size() > 0) + QByteArray encrChunk = dbFile.read(encryptedChunkSize); + qDebug() << "EncryptedDb::pullFileContent: got chunk:" << encrChunk.size(); + buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); + if (buf.size() > 0) { - fileContent += buffer; + fileContent += buf; } else { - qWarning() << "Encrypted history log is corrupted: can't decrypt"; - buffer = QByteArray(); + qWarning() << "EncryptedDb::pullFileContent: Encrypted history log is corrupted: can't decrypt, will be deleted"; + buf = QByteArray(); return false; } } @@ -88,31 +103,17 @@ bool EncryptedDb::pullFileContent() for (auto ba_line : splittedBA) { QString line = QByteArray::fromBase64(ba_line); - if (line.size() == 0) - continue; - - bool isGoodLine = false; - if (line.startsWith("CREATE", Qt::CaseInsensitive) || line.startsWith("INSERT", Qt::CaseInsensitive)) - { - if (line.endsWith(");")) - { - sqlCmds.append(line); - isGoodLine = true; - } - } - - if (!isGoodLine) - { - qWarning() << "Encrypted history log is corrupted: errors in content"; - buffer = QByteArray(); - return false; - } + sqlCmds.append(line); } + PlainDb::exec("BEGIN TRANSACTION;"); for (auto line : sqlCmds) { QSqlQuery r = PlainDb::exec(line); } + PlainDb::exec("COMMIT TRANSACTION;"); + + dbFile.close(); return true; } @@ -133,6 +134,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql) if (encr.size() > 0) { encrFile.write(encr); + encrFile.flush(); } buffer = buffer.right(buffer.size() - plainChunkSize); @@ -170,3 +172,14 @@ bool EncryptedDb::check(const QString &fname) file.close(); return state; } + +bool EncryptedDb::checkCmd(const QString &cmd) +{ + if (cmd.startsWith("INSERT", Qt::CaseInsensitive) || cmd.startsWith("UPDATE", Qt::CaseInsensitive) + || cmd.startsWith("DELETE", Qt::CaseInsensitive)) + { + return true; + } + + return false; +} diff --git a/src/misc/db/encrypteddb.h b/src/misc/db/encrypteddb.h index 29948c2b4..c27fb5c8c 100644 --- a/src/misc/db/encrypteddb.h +++ b/src/misc/db/encrypteddb.h @@ -32,10 +32,12 @@ public: static bool check(const QString &fname); private: - bool pullFileContent(); + bool pullFileContent(const QString& fname, QByteArray &buf); void appendToEncrypted(const QString &sql); + bool checkCmd(const QString &cmd); QFile encrFile; + QString fileName; static qint64 plainChunkSize; static qint64 encryptedChunkSize; diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 962fff3aa..65640ca21 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -63,6 +63,13 @@ void Settings::resetInstance() } } +void Settings::switchProfile(const QString& profile) +{ + setCurrentProfile(profile); + save(false); + resetInstance(); +} + void Settings::load() { if (loaded) @@ -191,13 +198,6 @@ void Settings::load() splitterState = s.value("splitterState", QByteArray()).toByteArray(); s.endGroup(); - s.beginGroup("Privacy"); - typingNotification = s.value("typingNotification", true).toBool(); - enableLogging = s.value("enableLogging", false).toBool(); - encryptLogs = s.value("encryptLogs", false).toBool(); - encryptTox = s.value("encryptTox", false).toBool(); - s.endGroup(); - s.beginGroup("Audio"); inDev = s.value("inDev", "").toString(); outDev = s.value("outDev", "").toString(); @@ -230,38 +230,45 @@ void Settings::load() loaded = true; - if (currentProfile.isEmpty()) // new profile in Core::switchConfiguration - return; + if (!currentProfile.isEmpty()) // new profile in Core::switchConfiguration + { + // load from a profile specific friend data list if possible + QString tmp = dir.filePath(currentProfile + ".ini"); + if (QFile(tmp).exists()) // otherwise, filePath remains the global file + filePath = tmp; - // load from a profile specific friend data list if possible - QString tmp = dir.filePath(currentProfile + ".ini"); - if (QFile(tmp).exists()) - filePath = tmp; + QSettings ps(filePath, QSettings::IniFormat); + friendLst.clear(); + ps.beginGroup("Friends"); + int size = ps.beginReadArray("Friend"); + for (int i = 0; i < size; i ++) + { + ps.setArrayIndex(i); + friendProp fp; + fp.addr = ps.value("addr").toString(); + fp.alias = ps.value("alias").toString(); + fp.autoAcceptDir = ps.value("autoAcceptDir").toString(); + friendLst[ToxID::fromString(fp.addr).publicKey] = fp; + } + ps.endArray(); + ps.endGroup(); - QSettings fs(filePath, QSettings::IniFormat); - friendLst.clear(); - fs.beginGroup("Friends"); - int size = fs.beginReadArray("Friend"); - for (int i = 0; i < size; i ++) - { - fs.setArrayIndex(i); - friendProp fp; - fp.addr = fs.value("addr").toString(); - fp.alias = fs.value("alias").toString(); - fp.autoAcceptDir = fs.value("autoAcceptDir").toString(); - friendLst[ToxID::fromString(fp.addr).publicKey] = fp; - } - fs.endArray(); - fs.endGroup(); + ps.beginGroup("Privacy"); + typingNotification = ps.value("typingNotification", false).toBool(); + enableLogging = ps.value("enableLogging", false).toBool(); + encryptLogs = ps.value("encryptLogs", false).toBool(); + encryptTox = ps.value("encryptTox", false).toBool(); + ps.endGroup(); + } } -void Settings::save(bool writeFriends) +void Settings::save(bool writePersonal) { QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); - save(filePath, writeFriends); + save(filePath, writePersonal); } -void Settings::save(QString path, bool writeFriends) +void Settings::save(QString path, bool writePersonal) { qDebug() << "Settings: Saving in "< - - This file is part of qTox, a Qt-based graphical interface for Tox. - - This program is libre 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 COPYING file for more details. -*/ - -#include "inputpassworddialog.h" -#include "ui_inputpassworddialog.h" - -InputPasswordDialog::InputPasswordDialog(QString title, QWidget *parent) : - QDialog(parent), - ui(new Ui::InputPasswordDialog) -{ - ui->setupUi(this); - - if (title != QString()) - setWindowTitle(title); -} - -InputPasswordDialog::~InputPasswordDialog() -{ - delete ui; -} - -QString InputPasswordDialog::getPassword() -{ - return ui->passwordLineEdit->text(); -} diff --git a/src/widget/form/inputpassworddialog.h b/src/widget/form/inputpassworddialog.h deleted file mode 100644 index 00ce6fc99..000000000 --- a/src/widget/form/inputpassworddialog.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (C) 2014 by Project Tox - - This file is part of qTox, a Qt-based graphical interface for Tox. - - This program is libre 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 COPYING file for more details. -*/ - -#ifndef INPUTPASSWORDDIALOG_H -#define INPUTPASSWORDDIALOG_H - -#include - -namespace Ui { -class InputPasswordDialog; -} - -class InputPasswordDialog : public QDialog -{ - Q_OBJECT - -public: - explicit InputPasswordDialog(QString title = QString(), QWidget *parent = 0); - ~InputPasswordDialog(); - - QString getPassword(); - -private: - Ui::InputPasswordDialog *ui; -}; - -#endif // INPUTPASSWORDDIALOG_H diff --git a/src/widget/form/inputpassworddialog.ui b/src/widget/form/inputpassworddialog.ui deleted file mode 100644 index 69ff9a2ea..000000000 --- a/src/widget/form/inputpassworddialog.ui +++ /dev/null @@ -1,97 +0,0 @@ - - - InputPasswordDialog - - - - 0 - 0 - 400 - 123 - - - - Password Dialog - - - true - - - - - - Input password: - - - - - - - IBeamCursor - - - QLineEdit::Password - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - InputPasswordDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - InputPasswordDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/widget/form/setpassworddialog.cpp b/src/widget/form/setpassworddialog.cpp index 402ff0fa7..9e63f8f99 100644 --- a/src/widget/form/setpassworddialog.cpp +++ b/src/widget/form/setpassworddialog.cpp @@ -18,14 +18,27 @@ #include "ui_setpassworddialog.h" #include -SetPasswordDialog::SetPasswordDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::SetPasswordDialog) +const double SetPasswordDialog::reasonablePasswordLength = 8.; + +SetPasswordDialog::SetPasswordDialog(QString body, QString extraButton, QWidget* parent) + : QDialog(parent) + , ui(new Ui::SetPasswordDialog) + , body(body+"\n") { ui->setupUi(this); connect(ui->passwordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit())); connect(ui->repasswordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit())); + + ui->body->setText(body + "\n" + tr("The passwords don't match.")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + if (!extraButton.isEmpty()) + { + QPushButton* third = new QPushButton(extraButton); + ui->buttonBox->addButton(third, QDialogButtonBox::YesRole); + connect(third, &QPushButton::clicked, this, [=](){this->done(2);}); + } } SetPasswordDialog::~SetPasswordDialog() @@ -35,10 +48,53 @@ SetPasswordDialog::~SetPasswordDialog() void SetPasswordDialog::onPasswordEdit() { - if (ui->passwordlineEdit->text() == ui->repasswordlineEdit->text()) + QString pswd = ui->passwordlineEdit->text(); + + if (pswd == ui->repasswordlineEdit->text() && pswd.length() > 0) + { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + ui->body->setText(body); + } else + { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->body->setText(body + tr("The passwords don't match.")); + } + + // Password strength calculator + // Based on code in the Master Password dialog in Firefox + // (pref-masterpass.js) + // Original code triple-licensed under the MPL, GPL, and LGPL + // so is license-compatible with this file + + const double lengthFactor = reasonablePasswordLength / 8.0; + int pwlength = (int)(pswd.length() / lengthFactor); + if (pwlength > 5) + pwlength = 5; + + const QRegExp numRxp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp); + int numeric = (int)(pswd.count(numRxp) / lengthFactor); + if (numeric > 3) + numeric = 3; + + const QRegExp symbRxp("\\W", Qt::CaseInsensitive, QRegExp::RegExp); + int numsymbols = (int)(pswd.count(symbRxp) / lengthFactor); + if (numsymbols > 3) + numsymbols = 3; + + const QRegExp upperRxp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp); + int upper = (int)(pswd.count(upperRxp) / lengthFactor); + if (upper > 3) + upper = 3; + + int pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10); + if (pwstrength < 0) + pwstrength = 0; + + if (pwstrength > 100) + pwstrength = 100; + + ui->strengthBar->setValue(pwstrength); } QString SetPasswordDialog::getPassword() diff --git a/src/widget/form/setpassworddialog.h b/src/widget/form/setpassworddialog.h index a0d388f33..e264b79d9 100644 --- a/src/widget/form/setpassworddialog.h +++ b/src/widget/form/setpassworddialog.h @@ -28,7 +28,7 @@ class SetPasswordDialog : public QDialog Q_OBJECT public: - explicit SetPasswordDialog(QWidget *parent = 0); + explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0); ~SetPasswordDialog(); QString getPassword(); @@ -37,6 +37,8 @@ private slots: private: Ui::SetPasswordDialog *ui; + QString body; + static const double reasonablePasswordLength; }; #endif // SETPASSWORDDIALOG_H diff --git a/src/widget/form/setpassworddialog.ui b/src/widget/form/setpassworddialog.ui index 6dfe441a8..35a7deeeb 100644 --- a/src/widget/form/setpassworddialog.ui +++ b/src/widget/form/setpassworddialog.ui @@ -6,44 +6,77 @@ 0 0 - 434 - 175 + 325 + 160 - Dialog + Set your password true - - - Type Password - - + - - - QLineEdit::Password - - - - - - - Repeat Password - - - - - - - QLineEdit::Password - - + + + + + Qt::AlignRight + + + Repeat password + + + + + + + Qt::AlignRight + + + Type password + + + + + + + QLineEdit::Password + + + + + + + QLineEdit::Password + + + + + + + Qt::AlignRight + + + Password strength + + + + + + + 0 + + + + + + + @@ -73,6 +106,10 @@ + + passwordlineEdit + repasswordlineEdit + diff --git a/src/widget/form/settings/identityform.cpp b/src/widget/form/settings/identityform.cpp index a5cfc2aa6..8d067b606 100644 --- a/src/widget/form/settings/identityform.cpp +++ b/src/widget/form/settings/identityform.cpp @@ -29,7 +29,6 @@ #include #include #include -#include IdentityForm::IdentityForm() : GenericForm(tr("Identity"), QPixmap(":/img/settings/identity.png")) @@ -152,10 +151,11 @@ void IdentityForm::onRenameClicked() name = Core::sanitize(name); QDir dir(Settings::getSettingsDirPath()); QString file = dir.filePath(name+Core::TOX_EXT); - if (!QFile::exists(file) || checkContinue(tr("Profile already exists", "rename confirm title"), + if (!QFile::exists(file) || Widget::getInstance()->askQuestion(tr("Profile already exists", "rename confirm title"), tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(cur))) { QFile::rename(dir.filePath(cur+Core::TOX_EXT), file); + QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini")); bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name); HistoryKeeper::renameHistory(cur, name); Settings::getInstance().setCurrentProfile(name); @@ -176,8 +176,6 @@ void IdentityForm::onExportClicked() if (QFile::exists(path)) { // should we popup a warning? - // if (!checkContinue(tr("Overwriting a file"), tr("Are you sure you want to overwrite %1?").arg(path))) - // return; success = QFile::remove(path); if (!success) { @@ -199,8 +197,8 @@ void IdentityForm::onDeleteClicked() } else { - if (checkContinue(tr("Deletion imminent!","deletion confirmation title"), - tr("Are you sure you want to delete this profile?\nAssociated friend information and chat logs will be deleted as well.","deletion confirmation text"))) + if (Widget::getInstance()->askQuestion(tr("Deletion imminent!","deletion confirmation title"), + tr("Are you sure you want to delete this profile?","deletion confirmation text"))) { QString profile = bodyUI->profiles->currentText(); QDir dir(Settings::getSettingsDirPath()); @@ -238,7 +236,7 @@ void IdentityForm::onImportClicked() QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); - if (QFileInfo(profilePath).exists() && !checkContinue(tr("Profile already exists", "import confirm title"), + if (QFileInfo(profilePath).exists() && !Widget::getInstance()->askQuestion(tr("Profile already exists", "import confirm title"), tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile))) return; @@ -251,12 +249,6 @@ void IdentityForm::onNewClicked() emit Widget::getInstance()->changeProfile(QString()); } -bool IdentityForm::checkContinue(const QString& title, const QString& msg) -{ - QMessageBox::StandardButton resp = QMessageBox::question(this, title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - return resp == QMessageBox::Yes; -} - void IdentityForm::disableSwitching() { bodyUI->loadButton->setEnabled(false); diff --git a/src/widget/form/settings/identityform.h b/src/widget/form/settings/identityform.h index 843d7e7b1..fb2da7839 100644 --- a/src/widget/form/settings/identityform.h +++ b/src/widget/form/settings/identityform.h @@ -66,7 +66,6 @@ private slots: void onDeleteClicked(); void onImportClicked(); void onNewClicked(); - bool checkContinue(const QString& title, const QString& msg); void disableSwitching(); void enableSwitching(); diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 928ed7538..3582cf82d 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -23,6 +23,8 @@ #include "src/widget/widget.h" #include "src/widget/form/setpassworddialog.h" #include +#include +#include PrivacyForm::PrivacyForm() : GenericForm(tr("Privacy"), QPixmap(":/img/settings/privacy.png")) @@ -30,10 +32,15 @@ PrivacyForm::PrivacyForm() : bodyUI = new Ui::PrivacySettings; bodyUI->setupUi(this); + bodyUI->encryptToxHLayout->addStretch(); + bodyUI->encryptLogsHLayout->addStretch(); + connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated())); connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated())); connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated())); + connect(bodyUI->changeLogsPwButton, &QPushButton::clicked, this, &PrivacyForm::setChatLogsPassword); connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated())); + connect(bodyUI->changeToxPwButton, &QPushButton::clicked, this, &PrivacyForm::setToxPassword); connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit())); @@ -48,7 +55,7 @@ void PrivacyForm::onEnableLoggingUpdated() { Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked()); - HistoryKeeper::getInstance()->resetInstance(); + HistoryKeeper::resetInstance(); Widget::getInstance()->clearAllReceipts(); } @@ -57,79 +64,172 @@ void PrivacyForm::onTypingNotificationEnabledUpdated() Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked()); } +bool PrivacyForm::setChatLogsPassword() +{ + Core* core = Core::getInstance(); + SetPasswordDialog* dialog; + QString body = tr("Please set your new chat log password."); + if (core->isPasswordSet(Core::ptMain)) + dialog = new SetPasswordDialog(body, tr("Use data file password", "pushbutton text"), this); + else + dialog = new SetPasswordDialog(body, QString(), this); + + if (int r = dialog->exec()) + { + QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(); + + QString newpw = dialog->getPassword(); + delete dialog; + + if (r == 2) + core->useOtherPassword(Core::ptHistory); + else + core->setPassword(newpw, Core::ptHistory); + + //if (!HistoryKeeper::checkPassword(true)) + // if (checkContinue(tr("Old encrypted chat logs", "title"), + // tr("Would you like to re-encrypt your old chat logs?\nOtherwise they will be deleted.", "body"))) + // for now, don't bother asking. Why wouldn't you want to reencrypt? + + Settings::getInstance().setEncryptLogs(true); + HistoryKeeper::getInstance()->importMessages(oldMessages); + + return true; + } + else + { + delete dialog; + return false; + } +} + void PrivacyForm::onEncryptLogsUpdated() { - bool encrytionState = bodyUI->cbEncryptHistory->isChecked(); + Core* core = Core::getInstance(); - if (encrytionState) + if (bodyUI->cbEncryptHistory->isChecked()) { - if (!Core::getInstance()->isPasswordSet(Core::ptHistory)) + if (!core->isPasswordSet(Core::ptHistory)) { - SetPasswordDialog dialog; - if (dialog.exec()) + if (setChatLogsPassword()) { - QString pswd = dialog.getPassword(); - if (pswd.size() == 0) - encrytionState = false; - - Core::getInstance()->setPassword(pswd, Core::ptHistory); - } else { - encrytionState = false; - Core::getInstance()->clearPassword(Core::ptHistory); + bodyUI->cbEncryptHistory->setChecked(true); + bodyUI->changeLogsPwButton->setEnabled(true); + return; } } } - - Settings::getInstance().setEncryptLogs(encrytionState); - if (encrytionState && !HistoryKeeper::checkPassword()) + else { - if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted log"), - tr("You already have history log file encrypted with different password\nDo you want to delete old history file?"), - QMessageBox::Ok | QMessageBox::Cancel)) + QMessageBox::StandardButton button = QMessageBox::warning( + Widget::getInstance(), + tr("Old encrypted chat logs", "title"), + tr("Would you like to decrypt your chat logs?\nOtherwise they will be deleted."), + QMessageBox::Ok | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel + ); + + if (button == QMessageBox::Ok) { - // TODO: ask user about reencryption with new password - encrytionState = false; + QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(true); + core->clearPassword(Core::ptHistory); + Settings::getInstance().setEncryptLogs(false); + HistoryKeeper::getInstance()->importMessages(oldMessages); + } + else if (button == QMessageBox::No) + { + if (QMessageBox::critical( + Widget::getInstance(), + tr("Old encrypted chat logs", "title"), + tr("Are you sure you want to lose your entire chat history?"), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel + ) + == QMessageBox::Yes) + { + HistoryKeeper::removeHistory(true); + } + else + { + bodyUI->cbEncryptHistory->setChecked(true); + return; + } + } + else + { + bodyUI->cbEncryptHistory->setChecked(true); + return; } } - Settings::getInstance().setEncryptLogs(encrytionState); - bodyUI->cbEncryptHistory->setChecked(encrytionState); + core->clearPassword(Core::ptHistory); + Settings::getInstance().setEncryptLogs(false); + bodyUI->cbEncryptHistory->setChecked(false); + bodyUI->changeLogsPwButton->setEnabled(false); + HistoryKeeper::resetInstance(); +} - if (encrytionState) - HistoryKeeper::resetInstance(); +bool PrivacyForm::setToxPassword() +{ + Core* core = Core::getInstance(); + SetPasswordDialog* dialog; + QString body = tr("Please set your new data file password."); + if (core->isPasswordSet(Core::ptHistory)) + dialog = new SetPasswordDialog(body, tr("Use chat log password", "pushbutton text"), this); + else + dialog = new SetPasswordDialog(body, QString(), this); - if (!Settings::getInstance().getEncryptLogs()) - Core::getInstance()->clearPassword(Core::ptHistory); + if (int r = dialog->exec()) + { + QString newpw = dialog->getPassword(); + delete dialog; + + if (r == 2) + core->useOtherPassword(Core::ptMain); + else + core->setPassword(newpw, Core::ptMain); + + Settings::getInstance().setEncryptTox(true); + core->saveConfiguration(); + return true; + } + else + { + delete dialog; + return false; + } } void PrivacyForm::onEncryptToxUpdated() { - bool encrytionState = bodyUI->cbEncryptTox->isChecked(); + Core* core = Core::getInstance(); - if (encrytionState) + if (bodyUI->cbEncryptTox->isChecked()) { - if (!Core::getInstance()->isPasswordSet(Core::ptMain)) + if (!core->isPasswordSet(Core::ptMain)) { - SetPasswordDialog dialog; - if (dialog.exec()) + if (setToxPassword()) { - QString pswd = dialog.getPassword(); - if (pswd.size() == 0) - encrytionState = false; - - Core::getInstance()->setPassword(pswd, Core::ptMain); - } else { - encrytionState = false; - Core::getInstance()->clearPassword(Core::ptMain); + bodyUI->cbEncryptTox->setChecked(true); + bodyUI->changeToxPwButton->setEnabled(true); + return; } } } + else + { + if (!Widget::getInstance()->askQuestion(tr("Decrypt your data file", "title"), tr("Would you like to decrypt your data file?"))) + { + bodyUI->cbEncryptTox->setChecked(true); + return; + } + // affirmative answer falls through to the catch all below + } - bodyUI->cbEncryptTox->setChecked(encrytionState); - Settings::getInstance().setEncryptTox(encrytionState); - - if (!Settings::getInstance().getEncryptTox()) - Core::getInstance()->clearPassword(Core::ptMain); + bodyUI->cbEncryptTox->setChecked(false); + Settings::getInstance().setEncryptTox(false); + bodyUI->changeToxPwButton->setEnabled(false); + core->clearPassword(Core::ptMain); } void PrivacyForm::setNospam() @@ -148,8 +248,10 @@ void PrivacyForm::present() bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs()); + bodyUI->changeLogsPwButton->setEnabled(Settings::getInstance().getEncryptLogs()); bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging()); bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox()); + bodyUI->changeToxPwButton->setEnabled(Settings::getInstance().getEncryptTox()); } void PrivacyForm::generateRandomNospam() diff --git a/src/widget/form/settings/privacyform.h b/src/widget/form/settings/privacyform.h index fc0e4eb0d..e0b7c70fc 100644 --- a/src/widget/form/settings/privacyform.h +++ b/src/widget/form/settings/privacyform.h @@ -39,7 +39,9 @@ private slots: void generateRandomNospam(); void onNospamEdit(); void onEncryptLogsUpdated(); + bool setChatLogsPassword(); void onEncryptToxUpdated(); + bool setToxPassword(); private: Ui::PrivacySettings* bodyUI; diff --git a/src/widget/form/settings/privacysettings.ui b/src/widget/form/settings/privacysettings.ui index 72262c327..983913d6c 100644 --- a/src/widget/form/settings/privacysettings.ui +++ b/src/widget/form/settings/privacysettings.ui @@ -57,7 +57,7 @@ - Keep History (unstable) + Keep chat logs (mostly stable) @@ -67,31 +67,63 @@ true - Encryption + Local file encryption - - - false - + - Encrypt Tox datafile + All internet communication is encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files. + + + true - - - false - - - Encrypt History - - - false - - + + + + + true + + + Encrypt Tox data file + + + + + + + Change password + + + + + + + + + + + true + + + Encrypt chat logs + + + true + + + + + + + Change password + + + + diff --git a/src/widget/toxsave.cpp b/src/widget/toxsave.cpp index 1b4a38fa9..9d14879db 100644 --- a/src/widget/toxsave.cpp +++ b/src/widget/toxsave.cpp @@ -30,12 +30,6 @@ void toxSaveEventHandler(const QByteArray& eventData) handleToxSave(eventData); } -static bool checkContinue(const QString& title, const QString& msg) -{ - QMessageBox::StandardButton resp = QMessageBox::question(Widget::getInstance(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - return resp == QMessageBox::Yes; -} - void handleToxSave(const QString& path) { Core* core = Core::getInstance(); @@ -67,7 +61,7 @@ void handleToxSave(const QString& path) QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); - if (QFileInfo(profilePath).exists() && !checkContinue(QObject::tr("Profile already exists", "import confirm title"), + if (QFileInfo(profilePath).exists() && !Widget::getInstance()->askQuestion(QObject::tr("Profile already exists", "import confirm title"), QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile))) return; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 73b9336cd..24871956d 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -32,7 +32,6 @@ #include "form/chatform.h" #include "maskablepixmapwidget.h" #include "src/historykeeper.h" -#include "form/inputpassworddialog.h" #include "src/autoupdate.h" #include "src/audio.h" #include "src/platform/timer.h" @@ -87,7 +86,7 @@ void Widget::init() icon = new QSystemTrayIcon(this); updateTrayIcon(); trayMenu = new QMenu; - + statusOnline = new QAction(tr("Online"), this); statusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png")); connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline())); @@ -99,19 +98,19 @@ void Widget::init() connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy())); actionQuit = new QAction(tr("&Quit"), this); connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); - + trayMenu->addAction(statusOnline); trayMenu->addAction(statusAway); trayMenu->addAction(statusBusy); trayMenu->addSeparator(); trayMenu->addAction(actionQuit); icon->setContextMenu(trayMenu); - + connect(icon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(onIconClick(QSystemTrayIcon::ActivationReason))); - + if (Settings::getInstance().getShowSystemTray()) { icon->show(); @@ -241,7 +240,6 @@ void Widget::init() connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); connect(core, &Core::avInvite, this, &Widget::playRingtone); connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection); - connect(core, &Core::blockingGetPassword, this, &Widget::getPassword, Qt::BlockingQueuedConnection); connect(core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged); connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int))); @@ -387,7 +385,7 @@ QString Widget::detectProfile() QString path, profile = Settings::getInstance().getCurrentProfile(); path = dir.filePath(profile + Core::TOX_EXT); QFile file(path); - if (profile == "" || !file.exists()) + if (profile.isEmpty() || !file.exists()) { Settings::getInstance().setCurrentProfile(""); #if 1 // deprecation attempt @@ -402,10 +400,13 @@ QString Widget::detectProfile() #endif { profile = askProfiles(); - if (profile != "") - return dir.filePath(profile + Core::TOX_EXT); - else + if (profile.isEmpty()) return ""; + else + { + Settings::getInstance().switchProfile(profile); + return dir.filePath(profile + Core::TOX_EXT); + } } } else @@ -1176,20 +1177,6 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result) g->getChatForm()->addSystemInfoMessage(tr("Message failed to send"), "red", QDateTime::currentDateTime()); } -void Widget::getPassword(QString info, int passtype, uint8_t* salt) -{ - Core::PasswordType pt = static_cast(passtype); - InputPasswordDialog dialog(info); - if (dialog.exec()) - { - QString pswd = dialog.getPassword(); - if (pswd.isEmpty()) - core->clearPassword(pt); - else - core->setPassword(pswd, pt, salt); - } -} - void Widget::onFriendTypingChanged(int friendId, bool isTyping) { Friend* f = FriendList::findFriend(friendId); @@ -1220,21 +1207,17 @@ void Widget::onSplitterMoved(int pos, int index) saveSplitterGeometry(); } -QMessageBox::StandardButton Widget::showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttons) +void Widget::showWarningMsgBox(const QString& title, const QString& msg) { // We can only display widgets from the GUI thread if (QThread::currentThread() != qApp->thread()) { - QMessageBox::StandardButton ret; QMetaObject::invokeMethod(this, "showWarningMsgBox", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QMessageBox::StandardButton, ret), - Q_ARG(const QString&, title), Q_ARG(const QString&, msg), - Q_ARG(QMessageBox::StandardButtons, buttons)); - return ret; + Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); } else { - return QMessageBox::warning(this, title, msg, buttons); + QMessageBox::warning(this, title, msg); } } @@ -1253,20 +1236,71 @@ void Widget::setEnabledThreadsafe(bool enabled) } } -bool Widget::askMsgboxQuestion(const QString& title, const QString& msg) +bool Widget::askQuestion(const QString& title, const QString& msg, bool defaultAns, bool warning) { // We can only display widgets from the GUI thread if (QThread::currentThread() != qApp->thread()) { bool ret; - QMetaObject::invokeMethod(this, "askMsgboxQuestion", Qt::BlockingQueuedConnection, + QMetaObject::invokeMethod(this, "askQuestion", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), - Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); + Q_ARG(const QString&, title), Q_ARG(const QString&, msg), + Q_ARG(bool, defaultAns), Q_ARG(bool, warning)); return ret; } else { - return QMessageBox::question(this, title, msg) == QMessageBox::StandardButton::Yes; + if (warning) + { + QMessageBox::StandardButton def = QMessageBox::Cancel; + if (defaultAns) + def = QMessageBox::Ok; + return QMessageBox::warning(this, title, msg, QMessageBox::Ok | QMessageBox::Cancel, def) == QMessageBox::Ok; + } + else + { + QMessageBox::StandardButton def = QMessageBox::No; + if (defaultAns) + def = QMessageBox::Yes; + return QMessageBox::question(this, title, msg, QMessageBox::Yes | QMessageBox::No, def) == QMessageBox::Yes; + } + } +} + +QString Widget::passwordDialog(const QString& cancel, const QString& body) +{ + // We can only display widgets from the GUI thread + if (QThread::currentThread() != qApp->thread()) + { + QString ret; + QMetaObject::invokeMethod(this, "passwordDialog", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, ret), + Q_ARG(const QString&, cancel), Q_ARG(const QString&, body)); + return ret; + } + else + { + QString ret; + QInputDialog dialog; + dialog.setWindowTitle(tr("Enter your password")); + dialog.setOkButtonText(tr("Set password")); + dialog.setCancelButtonText(cancel); + dialog.setInputMode(QInputDialog::TextInput); + dialog.setTextEchoMode(QLineEdit::Password); + dialog.setLabelText(body); + while (true) + { + int val = dialog.exec(); + if (val != QDialog::Accepted) + return QString(); + else + { + ret = dialog.textValue(); + if (!ret.isEmpty()) + return ret; + } + dialog.setLabelText(body + "\n" + tr("You must enter a non-empty password.")); + } } } diff --git a/src/widget/widget.h b/src/widget/widget.h index df3586773..b2b5d34b2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -64,10 +64,12 @@ public: void clearContactsList(); void setTranslation(); void updateTrayIcon(); - Q_INVOKABLE QMessageBox::StandardButton showWarningMsgBox(const QString& title, const QString& msg, - QMessageBox::StandardButtons buttonss = QMessageBox::Ok); + Q_INVOKABLE void showWarningMsgBox(const QString& title, const QString& msg); Q_INVOKABLE void setEnabledThreadsafe(bool enabled); - Q_INVOKABLE bool askMsgboxQuestion(const QString& title, const QString& msg); + Q_INVOKABLE bool askQuestion(const QString& title, const QString& msg, bool defaultAns = false, bool warning = true); + Q_INVOKABLE QString passwordDialog(const QString& cancel, const QString& body); + Q_INVOKABLE QString askProfiles(); + // hooray for threading hacks ~Widget(); virtual void closeEvent(QCloseEvent *event); @@ -133,7 +135,6 @@ private slots: void playRingtone(); void onIconClick(QSystemTrayIcon::ActivationReason); void onUserAwayCheck(); - void getPassword(QString info, int passtype, uint8_t* salt); void onFriendTypingChanged(int friendId, bool isTyping); void onSetShowSystemTray(bool newValue); void onSplitterMoved(int pos, int index); @@ -147,7 +148,6 @@ private: void removeGroup(Group* g, bool fake = false); void saveWindowGeometry(); void saveSplitterGeometry(); - QString askProfiles(); QString detectProfile(); QSystemTrayIcon *icon; QMenu *trayMenu;