1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'encryption' into master

Conflicts:
	src/core.cpp
	src/historykeeper.h
This commit is contained in:
Dubslow 2015-02-03 00:32:52 -06:00
commit a572ccff27
No known key found for this signature in database
GPG Key ID: 3DB8E05315C220AA
26 changed files with 1088 additions and 740 deletions

View File

@ -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
@ -116,7 +115,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
@ -193,7 +192,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 \
@ -226,6 +224,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 \
@ -262,7 +261,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 \

View File

@ -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();
}

View File

@ -23,7 +23,6 @@
#include "src/audio.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <ctime>
#include <functional>
@ -39,7 +38,6 @@
#include <QDateTime>
#include <QList>
#include <QBuffer>
#include <QMessageBox>
#include <QMutexLocker>
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,9 +186,6 @@ 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();
}
@ -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
@ -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*>(_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<uint8_t *>(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<uint8_t *>(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();
@ -1250,53 +1232,7 @@ bool Core::loadConfiguration(QString path)
// tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs())
{
// 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<uint8_t *>(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
{
bool error = true;
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;
@ -1307,6 +1243,9 @@ 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())) {
@ -1323,7 +1262,7 @@ 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);
@ -1331,66 +1270,6 @@ void Core::saveConfiguration()
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<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
}
void Core::switchConfiguration(const QString& profile)
{
if (profile.isEmpty())
@ -1399,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
@ -1421,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()
@ -1794,69 +1666,6 @@ QList<CString> 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<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1)
{
qWarning() << "Core::encryptData: encryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(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<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], decrypted) != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(decrypted), sz);
}
bool Core::isPasswordSet(PasswordType passtype)
{
if (pwsaltedkeys[passtype])
return true;
return false;
}
QString Core::getPeerName(const ToxID& id) const
{
QString name;
@ -1891,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)

View File

@ -52,6 +52,8 @@ public:
static QString sanitize(QString name);
static QList<CString> 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
@ -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;

327
src/coreencryption.cpp Normal file
View File

@ -0,0 +1,327 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
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 <tox/tox.h>
#include <tox/toxencryptsave.h>
#include "src/misc/settings.h"
#include "misc/cstring.h"
#include "historykeeper.h"
#include <QApplication>
#include <QDebug>
#include <QSaveFile>
#include <QFile>
#include <QThread>
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 <algorithm>
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<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1)
{
qWarning() << "Core::encryptData: encryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(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<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], decrypted);
if (decr_size != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(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<uint8_t *>(data.data()), salt);
if (err)
{
qWarning() << "Core: can't get salt from" << filename << "header";
return QByteArray();
}
QByteArray res = QByteArray::fromRawData(reinterpret_cast<const char*>(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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<uint8_t*>(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<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
Settings::getInstance().save();
}

View File

@ -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<QString> 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::HistMessage> 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::HistMessage> 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<HistMessage> 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<HistoryKeeper::HistMessage> &lst)
{
db->exec("BEGIN TRANSACTION;");
for (const HistMessage &msg : lst)
{
QList<QString> 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<QString> HistoryKeeper::generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent)
{
QList<QString> 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::HistMessage> 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;
}

View File

@ -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<HistMessage> 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<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
void markAsSent(int m_id);
QList<HistMessage> exportMessages();
void importMessages(const QList<HistoryKeeper::HistMessage> &lst);
void setSyncType(Db::syncType sType);
private:
@ -63,15 +73,16 @@ private:
void updateAliases();
QPair<int, ChatType> getChatID(const QString &id_str, ChatType ct);
int getAliasID(const QString &id_str);
QString wrapMessage(const QString &str);
QString unWrapMessage(const QString &str);
QList<QString> generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent);
static QString wrapMessage(const QString &str);
static QString unWrapMessage(const QString &str);
static ChatType convertToChatType(int);
ChatType convertToChatType(int);
GenericDdInterface *db;
QMap<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats;
int messageID;
qint64 messageID;
};
#endif // HISTORYKEEPER_H

View File

@ -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
{

View File

@ -24,28 +24,36 @@
#include <QDebug>
#include <QSqlError>
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<QString> 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;
}

View File

@ -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;

View File

@ -58,6 +58,13 @@ void Settings::resetInstance()
}
}
void Settings::switchProfile(const QString& profile)
{
setCurrentProfile(profile);
save(false);
resetInstance();
}
void Settings::load()
{
if (loaded)
@ -186,13 +193,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();
@ -225,38 +225,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 "<<path;
@ -336,13 +343,6 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("splitterState", splitterState);
s.endGroup();
s.beginGroup("Privacy");
s.setValue("typingNotification", typingNotification);
s.setValue("enableLogging", enableLogging);
s.setValue("encryptLogs", encryptLogs);
s.setValue("encryptTox", encryptTox);
s.endGroup();
s.beginGroup("Audio");
s.setValue("inDev", inDev);
s.setValue("outDev", outDev);
@ -353,23 +353,30 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("camVideoRes",camVideoRes);
s.endGroup();
if (!writeFriends || currentProfile.isEmpty()) // Core::switchConfiguration
return;
if (writePersonal && !currentProfile.isEmpty()) // Core::switchConfiguration
{
QSettings ps(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
ps.beginGroup("Friends");
ps.beginWriteArray("Friend", friendLst.size());
int index = 0;
for (auto& frnd : friendLst)
{
ps.setArrayIndex(index);
ps.setValue("addr", frnd.addr);
ps.setValue("alias", frnd.alias);
ps.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
ps.endArray();
ps.endGroup();
QSettings fs(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
fs.beginGroup("Friends");
fs.beginWriteArray("Friend", friendLst.size());
int index = 0;
for (auto& frnd : friendLst)
{
fs.setArrayIndex(index);
fs.setValue("addr", frnd.addr);
fs.setValue("alias", frnd.alias);
fs.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
fs.endArray();
fs.endGroup();
ps.beginGroup("Privacy");
ps.setValue("typingNotification", typingNotification);
ps.setValue("enableLogging", enableLogging);
ps.setValue("encryptLogs", encryptLogs);
ps.setValue("encryptTox", encryptTox);
ps.endGroup();
}
}
QString Settings::getSettingsDirPath()

View File

@ -32,6 +32,7 @@ class Settings : public QObject
public:
static Settings& getInstance();
static void resetInstance();
void switchProfile(const QString& profile);
~Settings() = default;
void executeSettingsDialog(QWidget* parent);
@ -236,8 +237,8 @@ public:
void setCompactLayout(bool compact);
public:
void save(bool writeFriends = true);
void save(QString path, bool writeFriends = true);
void save(bool writePersonal = true);
void save(QString path, bool writePersonal = true);
void load();
private:

View File

@ -1,38 +0,0 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
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();
}

View File

@ -1,40 +0,0 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
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 <QDialog>
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

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InputPasswordDialog</class>
<widget class="QDialog" name="InputPasswordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>123</height>
</rect>
</property>
<property name="windowTitle">
<string>Password Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Input password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>InputPasswordDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>InputPasswordDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -18,14 +18,27 @@
#include "ui_setpassworddialog.h"
#include <QPushButton>
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-> 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(Tertiary);});
}
}
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()

View File

@ -28,7 +28,8 @@ class SetPasswordDialog : public QDialog
Q_OBJECT
public:
explicit SetPasswordDialog(QWidget *parent = 0);
enum ReturnCode {Rejected=QDialog::Rejected, Accepted=QDialog::Accepted, Tertiary};
explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0);
~SetPasswordDialog();
QString getPassword();
@ -37,6 +38,8 @@ private slots:
private:
Ui::SetPasswordDialog *ui;
QString body;
static const double reasonablePasswordLength;
};
#endif // SETPASSWORDDIALOG_H

View File

@ -6,44 +6,77 @@
<rect>
<x>0</x>
<y>0</y>
<width>434</width>
<height>175</height>
<width>325</width>
<height>160</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Dialog</string>
<string>Set your password</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Type Password</string>
</property>
</widget>
<widget class="QLabel" name="body"/>
</item>
<item>
<widget class="QLineEdit" name="passwordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Repeat Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="repasswordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<layout class="QGridLayout" name="pswdsLayout">
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Repeat password</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Type password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="repasswordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="passwordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Password strength</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QProgressBar" name="strengthBar">
<property name="value">
<number>0</number>
</property>
<property name="format">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
@ -73,6 +106,10 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>passwordlineEdit</tabstop>
<tabstop>repasswordlineEdit</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@ -29,7 +29,6 @@
#include <QClipboard>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
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);

View File

@ -66,7 +66,6 @@ private slots:
void onDeleteClicked();
void onImportClicked();
void onNewClicked();
bool checkContinue(const QString& title, const QString& msg);
void disableSwitching();
void enableSwitching();

View File

@ -23,6 +23,8 @@
#include "src/widget/widget.h"
#include "src/widget/form/setpassworddialog.h"
#include <QMessageBox>
#include <QFile>
#include <QDebug>
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,183 @@ 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);
// check if an encrypted history exists because it was disabled earlier, and use it if possible
QString path = HistoryKeeper::getHistoryPath(QString(), 1);
QByteArray salt = core->getSaltFromFile(path);
bool haveEncHist = salt.size() > 0;
do {
int r = dialog->exec();
if (r == QDialog::Rejected)
break;
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile();
QString newpw = dialog->getPassword();
if (r == SetPasswordDialog::Tertiary)
core->useOtherPassword(Core::ptHistory);
else if (haveEncHist)
core->setPassword(newpw, Core::ptHistory, reinterpret_cast<uint8_t*>(salt.data()));
else
core->setPassword(newpw, Core::ptHistory);
if (!haveEncHist || HistoryKeeper::checkPassword(1))
{
Settings::getInstance().setEncryptLogs(true);
HistoryKeeper::getInstance()->importMessages(oldMessages);
Widget::getInstance()->reloadHistory();
delete dialog;
return true;
}
else
{
if (!Widget::getInstance()->askQuestion(tr("Old encrypted chat log", "popup title"), tr("There is currently an unused encrypted chat log, but the password you just entered doesn't match.\nWould you like to try again?")))
haveEncHist = false; // logically this is really just a `break`, but conceptually this is more accurate
}
} while (haveEncHist);
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<HistoryKeeper::HistMessage> 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 == SetPasswordDialog::Tertiary)
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 +259,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()

View File

@ -39,7 +39,9 @@ private slots:
void generateRandomNospam();
void onNospamEdit();
void onEncryptLogsUpdated();
bool setChatLogsPassword();
void onEncryptToxUpdated();
bool setToxPassword();
private:
Ui::PrivacySettings* bodyUI;

View File

@ -58,7 +58,7 @@
Save format changes are possible, which may result in data loss.</string>
</property>
<property name="text">
<string>Keep History (unstable)</string>
<string>Keep chat logs (mostly stable)</string>
</property>
</widget>
</item>
@ -68,31 +68,63 @@ Save format changes are possible, which may result in data loss.</string>
<bool>true</bool>
</property>
<property name="title">
<string>Encryption</string>
<string>Local file encryption</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>false</bool>
</property>
<widget class="QLabel" name="encryptStatement">
<property name="text">
<string>Encrypt Tox datafile</string>
<string>All internet communication is encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Encrypt History</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
<layout class="QHBoxLayout" name="encryptToxHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt Tox data file</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeToxPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="encryptLogsHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt chat logs</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeLogsPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -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;

View File

@ -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"
@ -242,7 +241,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)));
@ -390,7 +388,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
@ -405,10 +403,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
@ -439,10 +440,7 @@ QString Widget::askProfiles()
false, // if the user can enter their own input
&ok);
if (!ok) // user cancelled
{
qApp->quit();
return "";
}
else
return profile;
}
@ -688,6 +686,12 @@ void Widget::setStatusMessage(const QString &statusMessage)
ui->statusLabel->setToolTip(statusMessage); // for overlength messsages
}
void Widget::reloadHistory()
{
for (auto f : FriendList::getAllFriends())
f->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true);
}
void Widget::addFriend(int friendId, const QString &userId)
{
//qDebug() << "Widget: Adding friend with id" << userId;
@ -1187,20 +1191,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<Core::PasswordType>(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);
@ -1231,21 +1221,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);
}
}
@ -1264,20 +1250,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("Decrypt"));
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."));
}
}
}

View File

@ -65,10 +65,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);
@ -76,6 +78,7 @@ public:
virtual void resizeEvent(QResizeEvent *event);
void clearAllReceipts();
void reloadHistory();
void reloadTheme();
@ -134,7 +137,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);
@ -148,7 +150,6 @@ private:
void removeGroup(Group* g, bool fake = false);
void saveWindowGeometry();
void saveSplitterGeometry();
QString askProfiles();
QString detectProfile();
SystemTrayIcon *icon;
QMenu *trayMenu;