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/autoupdate.cpp
	src/core.cpp
	src/core.h
	src/misc/settings.cpp
	src/widget/widget.cpp
This commit is contained in:
Dubslow 2015-01-27 18:28:44 -06:00
commit 2dc5cea1d2
No known key found for this signature in database
GPG Key ID: 3DB8E05315C220AA
26 changed files with 1065 additions and 734 deletions

View File

@ -32,7 +32,6 @@ FORMS += \
src/widget/form/settings/identitysettings.ui \ src/widget/form/settings/identitysettings.ui \
src/widget/form/settings/privacysettings.ui \ src/widget/form/settings/privacysettings.ui \
src/widget/form/loadhistorydialog.ui \ src/widget/form/loadhistorydialog.ui \
src/widget/form/inputpassworddialog.ui \
src/widget/form/setpassworddialog.ui \ src/widget/form/setpassworddialog.ui \
src/widget/form/settings/advancedsettings.ui src/widget/form/settings/advancedsettings.ui
@ -99,7 +98,7 @@ win32 {
target.path = /usr/bin target.path = /usr/bin
INSTALLS += target 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 += -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 LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
} else { } else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc 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/genericddinterface.h \
src/misc/db/plaindb.h \ src/misc/db/plaindb.h \
src/misc/db/encrypteddb.h \ src/misc/db/encrypteddb.h \
src/widget/form/inputpassworddialog.h \
src/widget/form/setpassworddialog.h \ src/widget/form/setpassworddialog.h \
src/widget/form/tabcompleter.h \ src/widget/form/tabcompleter.h \
src/video/videoframe.h \ src/video/videoframe.h \
@ -207,6 +205,7 @@ SOURCES += \
src/widget/groupwidget.cpp \ src/widget/groupwidget.cpp \
src/widget/widget.cpp \ src/widget/widget.cpp \
src/core.cpp \ src/core.cpp \
src/coreencryption.cpp \
src/friend.cpp \ src/friend.cpp \
src/friendlist.cpp \ src/friendlist.cpp \
src/group.cpp \ src/group.cpp \
@ -243,7 +242,6 @@ SOURCES += \
src/misc/db/genericddinterface.cpp \ src/misc/db/genericddinterface.cpp \
src/misc/db/plaindb.cpp \ src/misc/db/plaindb.cpp \
src/misc/db/encrypteddb.cpp \ src/misc/db/encrypteddb.cpp \
src/widget/form/inputpassworddialog.cpp \
src/widget/form/setpassworddialog.cpp \ src/widget/form/setpassworddialog.cpp \
src/video/netvideosource.cpp \ src/video/netvideosource.cpp \
src/widget/form/tabcompleter.cpp \ src/widget/form/tabcompleter.cpp \

View File

@ -490,8 +490,8 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
QDir updateDir(updateDirStr); QDir updateDir(updateDirStr);
if ((updateDir.exists() && QFile(updateDirStr+"flist").exists()) if ((updateDir.exists() && QFile(updateDirStr+"flist").exists())
|| Widget::getInstance()->askMsgboxQuestion(QObject::tr("Update", "The title of a message box"), || 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."))) QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."), true, false))
{ {
downloadUpdate(); downloadUpdate();
} }

View File

@ -23,7 +23,6 @@
#include "src/audio.h" #include "src/audio.h"
#include <tox/tox.h> #include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
@ -39,7 +38,6 @@
#include <QDateTime> #include <QDateTime>
#include <QList> #include <QList>
#include <QBuffer> #include <QBuffer>
#include <QMessageBox>
#include <QMutexLocker> #include <QMutexLocker>
const QString Core::CONFIG_FILE_NAME = "data"; const QString Core::CONFIG_FILE_NAME = "data";
@ -90,13 +88,24 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) :
Audio::openInput(inDevDescr); Audio::openInput(inDevDescr);
} }
void Core::deadifyTox()
{
if (toxav)
{
toxav_kill(toxav);
toxav = nullptr;
}
if (tox)
{
tox_kill(tox);
tox = nullptr;
}
}
Core::~Core() Core::~Core()
{ {
qDebug() << "Deleting Core"; qDebug() << "Deleting Core";
clearPassword(Core::ptMain);
clearPassword(Core::ptHistory);
toxTimer->stop(); toxTimer->stop();
coreThread->exit(0); coreThread->exit(0);
while (coreThread->isRunning()) while (coreThread->isRunning())
@ -105,13 +114,7 @@ Core::~Core()
coreThread->wait(500); coreThread->wait(500);
} }
if (tox) deadifyTox();
{
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
if (videobuf) if (videobuf)
{ {
@ -183,12 +186,9 @@ void Core::make_tox()
{ {
if (toxOptions.proxy_type != TOX_PROXY_NONE) 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!"; qCritical() << "Core: bad proxy! no toxcore!";
emit badProxy(); emit badProxy();
} }
else else
{ {
qCritical() << "Tox core failed to start"; qCritical() << "Tox core failed to start";
@ -229,14 +229,26 @@ void Core::start()
if (loadPath != "") if (loadPath != "")
{ {
if (!loadConfiguration(loadPath)) // loadPath is meaningless after this while (!loadConfiguration(loadPath))
{ {
qCritical() << "Core: loadConfiguration failed, exiting now"; if (loadPath.isEmpty())
emit failedToStart(); {
tox_kill(tox); QString profile;
tox = nullptr; if ((profile = loadOldInformation()).isEmpty())
return; {
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 = ""; loadPath = "";
} }
else // new ID else // new ID
@ -292,7 +304,7 @@ void Core::start()
} }
else else
qDebug() << "Core: Error loading self avatar"; qDebug() << "Core: Error loading self avatar";
ready = true; ready = true;
process(); // starts its own timer process(); // starts its own timer
@ -309,7 +321,7 @@ void Core::start()
void Core::process() void Core::process()
{ {
if (!tox) if (!isReady())
return; return;
static int tolerance = CORE_DISCONNECT_TOLERANCE; 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) void Core::onGroupMessage(Tox*, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *_core)
{ {
Core* core = static_cast<Core*>(_core); Core* core = static_cast<Core*>(_core);
emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false); 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) void Core::removeFriend(int friendId, bool fake)
{ {
if (!tox || fake) if (!isReady() || fake)
return; return;
if (tox_del_friend(tox, friendId) == -1) { if (tox_del_friend(tox, friendId) == -1) {
emit failedToRemoveFriend(friendId); emit failedToRemoveFriend(friendId);
@ -1024,7 +1035,7 @@ void Core::removeFriend(int friendId, bool fake)
void Core::removeGroup(int groupId, bool fake) void Core::removeGroup(int groupId, bool fake)
{ {
if (!tox || fake) if (!isReady() || fake)
return; return;
tox_del_groupchat(tox, groupId); tox_del_groupchat(tox, groupId);
@ -1162,7 +1173,8 @@ QString Core::sanitize(QString name)
bool Core::loadConfiguration(QString path) 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); QFile configurationFile(path);
qDebug() << "Core::loadConfiguration: reading from " << path; qDebug() << "Core::loadConfiguration: reading from " << path;
@ -1186,51 +1198,21 @@ bool Core::loadConfiguration(QString path)
} }
else if (error == 1) // Encrypted data save else if (error == 1) // Encrypted data save
{ {
if (!Settings::getInstance().getEncryptTox()) if (!loadEncryptedSave(data))
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
{ {
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); loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
if (!pwsaltedkeys[ptMain]) Settings::getInstance().switchProfile(profile);
Widget::getInstance()->showWarningMsgBox(tr("Password error"), tr("Failed to setup password.\nEmpty password.")); HistoryKeeper::resetInstance();
} }
return false;
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);
} }
} }
configurationFile.close(); configurationFile.close();
@ -1239,7 +1221,7 @@ bool Core::loadConfiguration(QString path)
QString name = getUsername(); QString name = getUsername();
if (!name.isEmpty()) if (!name.isEmpty())
emit usernameSet(name); emit usernameSet(name);
QString msg = getStatusMessage(); QString msg = getStatusMessage();
if (!msg.isEmpty()) if (!msg.isEmpty())
emit statusMessageSet(msg); emit statusMessageSet(msg);
@ -1250,54 +1232,7 @@ bool Core::loadConfiguration(QString path)
// tox core is already decrypted // tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs())
{ checkEncryptedHistory();
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<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
{
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);
}
}
loadFriends(); loadFriends();
return true; return true;
@ -1308,13 +1243,16 @@ void Core::saveConfiguration()
if (QThread::currentThread() != coreThread) if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration"); return (void) QMetaObject::invokeMethod(this, "saveConfiguration");
if (!isReady())
return;
QString dir = Settings::getSettingsDirPath(); QString dir = Settings::getSettingsDirPath();
QDir directory(dir); QDir directory(dir);
if (!directory.exists() && !directory.mkpath(directory.absolutePath())) { if (!directory.exists() && !directory.mkpath(directory.absolutePath())) {
qCritical() << "Error while creating directory " << dir; qCritical() << "Error while creating directory " << dir;
return; return;
} }
QString profile = Settings::getInstance().getCurrentProfile(); QString profile = Settings::getInstance().getCurrentProfile();
if (profile == "") if (profile == "")
@ -1324,74 +1262,14 @@ void Core::saveConfiguration()
if (profile == "") // happens on creation of a new Tox ID if (profile == "") // happens on creation of a new Tox ID
profile = getIDString(); profile = getIDString();
Settings::getInstance().setCurrentProfile(profile); Settings::getInstance().switchProfile(profile);
} }
QString path = directory.filePath(profile + TOX_EXT); QString path = directory.filePath(profile + TOX_EXT);
saveConfiguration(path); 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) void Core::switchConfiguration(const QString& profile)
{ {
if (profile.isEmpty()) if (profile.isEmpty())
@ -1400,19 +1278,15 @@ void Core::switchConfiguration(const QString& profile)
qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile; qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration(); saveConfiguration();
saveCurrentInformation(); // part of a hack, see core.h
ready = false; ready = false;
Widget::getInstance()->setEnabledThreadsafe(false);
clearPassword(ptMain); clearPassword(ptMain);
clearPassword(ptHistory); clearPassword(ptHistory);
toxTimer->stop(); toxTimer->stop();
Widget::getInstance()->setEnabledThreadsafe(false); deadifyTox();
if (tox) {
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
emit selfAvatarChanged(QPixmap(":/img/contact_dark.png")); emit selfAvatarChanged(QPixmap(":/img/contact_dark.png"));
emit blockingClearContacts(); // we need this to block, but signals are required for thread safety 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 else
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
// the new profile needs to be set before resetting the settings, so that Settings::getInstance().switchProfile(profile);
// we don't load the old profile's profile.ini HistoryKeeper::resetInstance();
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();
start(); start();
Widget::getInstance()->setEnabledThreadsafe(true); if (isReady())
Widget::getInstance()->setEnabledThreadsafe(true);
} }
void Core::loadFriends() void Core::loadFriends()
@ -1795,69 +1666,6 @@ QList<CString> Core::splitMessage(const QString &message, int maxLen)
return splittedMsgs; 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 Core::getPeerName(const ToxID& id) const
{ {
QString name; QString name;
@ -1892,7 +1700,7 @@ QString Core::getPeerName(const ToxID& id) const
bool Core::isReady() bool Core::isReady()
{ {
return ready; return toxav && tox && ready;
} }
void Core::setNospam(uint32_t nospam) void Core::setNospam(uint32_t nospam)

View File

@ -46,12 +46,14 @@ public:
explicit Core(Camera* cam, QThread* coreThread, QString initialLoadPath); explicit Core(Camera* cam, QThread* coreThread, QString initialLoadPath);
static Core* getInstance(); ///< Returns the global widget's Core instance static Core* getInstance(); ///< Returns the global widget's Core instance
~Core(); ~Core();
static const QString TOX_EXT; static const QString TOX_EXT;
static const QString CONFIG_FILE_NAME; static const QString CONFIG_FILE_NAME;
static QString sanitize(QString name); static QString sanitize(QString name);
static QList<CString> splitMessage(const QString &message, int maxLen); static QList<CString> splitMessage(const QString &message, int maxLen);
static QByteArray getSaltFromFile(QString filename);
QString getPeerName(const ToxID& id) const; 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 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 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 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 void quitGroupChat(int groupId) const; ///< Quit a groupchat
QString getIDString() const; ///< Get the 12 first characters of our Tox ID 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 getUsername() const; ///< Returns our username, or an empty string on failure
QString getStatusMessage() const; ///< Returns our status message, 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 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 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 process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
void bootstrapDht(); ///< Connects us to the Tox network 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();
void saveConfiguration(const QString& path); void saveConfiguration(const QString& path);
void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core
void acceptFriendRequest(const QString& userId); void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message); void requestFriendship(const QString& friendAddress, const QString& message);
@ -137,6 +139,7 @@ public slots:
static bool isGroupCallVolEnabled(int groupId); static bool isGroupCallVolEnabled(int groupId);
void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr);
void useOtherPassword(PasswordType type);
void clearPassword(PasswordType passtype); void clearPassword(PasswordType passtype);
QByteArray encryptData(const QByteArray& data, PasswordType passtype); QByteArray encryptData(const QByteArray& data, PasswordType passtype);
QByteArray decryptData(const QByteArray& data, PasswordType passtype); QByteArray decryptData(const QByteArray& data, PasswordType passtype);
@ -145,7 +148,6 @@ signals:
void connected(); void connected();
void disconnected(); void disconnected();
void blockingClearContacts(); void blockingClearContacts();
void blockingGetPassword(QString info, int passtype, uint8_t* salt = nullptr);
void friendRequestReceived(const QString& userId, const QString& message); void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(int friendId, const QString& message, bool isAction); void friendMessageReceived(int friendId, const QString& message, bool isAction);
@ -266,6 +268,8 @@ private:
bool checkConnection(); bool checkConnection();
bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise
bool loadEncryptedSave(QByteArray& data);
void checkEncryptedHistory();
void make_tox(); void make_tox();
void loadFriends(); void loadFriends();
@ -274,6 +278,8 @@ private:
void checkLastOnline(int friendId); void checkLastOnline(int friendId);
void deadifyTox();
private slots: private slots:
void onFileTransferFinished(ToxFile file); void onFileTransferFinished(ToxFile file);
@ -294,7 +300,17 @@ private:
QMutex fileSendMutex, messageSendMutex; QMutex fileSendMutex, messageSendMutex;
bool ready; 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 const int videobufsize;
static uint8_t* videobuf; 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; return historyInstance;
} }
bool HistoryKeeper::checkPassword() bool HistoryKeeper::checkPassword(int encrypted)
{ {
if (Settings::getInstance().getEnableLogging()) if (!Settings::getInstance().getEnableLogging() && (encrypted == -1))
{
if (Settings::getInstance().getEncryptLogs())
{
QString dbpath = getHistoryPath();
return EncryptedDb::check(dbpath);
} else {
return true;
}
} else {
return true; return true;
}
if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs()))
return EncryptedDb::check(getHistoryPath(Settings::getInstance().getCurrentProfile(), encrypted));
return true;
} }
HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
@ -132,9 +127,10 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
setSyncType(Settings::getInstance().getDbSyncType()); setSyncType(Settings::getInstance().getDbSyncType());
messageID = 0;
QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";"); QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";");
sqlAnswer.first(); if (sqlAnswer.first())
messageID = sqlAnswer.value(0).toInt(); messageID = sqlAnswer.value(0).toLongLong();
} }
HistoryKeeper::~HistoryKeeper() HistoryKeeper::~HistoryKeeper()
@ -142,16 +138,13 @@ HistoryKeeper::~HistoryKeeper()
delete db; 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; QList<QString> cmds = generateAddChatEntryCmd(chat, message, sender, dt, isSent);
int sender_id = getAliasID(sender);
db->exec("BEGIN TRANSACTION;"); db->exec("BEGIN TRANSACTION;");
db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message) ") + for (auto &it : cmds)
QString("VALUES (%1, %2, %3, '%4');") db->exec(it);
.arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
db->exec(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent));
db->exec("COMMIT TRANSACTION;"); db->exec("COMMIT TRANSACTION;");
messageID++; messageID++;
@ -190,12 +183,66 @@ QList<HistoryKeeper::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::C
QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt); QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt);
res.push_back({id, sender,message,time,isSent}); res.push_back(HistMessage(id, "", sender, message, time, isSent));
} }
return res; 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 HistoryKeeper::wrapMessage(const QString &str)
{ {
QString wrappedMessage(str); QString wrappedMessage(str);
@ -272,7 +319,7 @@ void HistoryKeeper::resetInstance()
historyInstance = nullptr; 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(chat)
Q_UNUSED(message) Q_UNUSED(message)
@ -342,3 +389,29 @@ void HistoryKeeper::setSyncType(Db::syncType sType)
db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd)); 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 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; qint64 id;
QString chat;
QString sender; QString sender;
QString message; QString message;
QDateTime timestamp; QDateTime timestamp;
@ -44,14 +48,20 @@ public:
static void resetInstance(); static void resetInstance();
static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify 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 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); qint64 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 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); QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
void markAsSent(int m_id); void markAsSent(int m_id);
QList<HistMessage> exportMessages();
void importMessages(const QList<HistoryKeeper::HistMessage> &lst);
void setSyncType(Db::syncType sType); void setSyncType(Db::syncType sType);
private: private:
@ -65,13 +75,14 @@ private:
int getAliasID(const QString &id_str); int getAliasID(const QString &id_str);
QString wrapMessage(const QString &str); QString wrapMessage(const QString &str);
QString unWrapMessage(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);
ChatType convertToChatType(int); ChatType convertToChatType(int);
GenericDdInterface *db; GenericDdInterface *db;
QMap<QString, int> aliases; QMap<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats; QMap<QString, QPair<int, ChatType>> chats;
int messageID; qint64 messageID;
}; };
#endif // HISTORYKEEPER_H #endif // HISTORYKEEPER_H

View File

@ -76,7 +76,7 @@ int main(int argc, char *argv[])
if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox")) if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox"))
{ {
qDebug() << "Setting profile to" << profile; qDebug() << "Setting profile to" << profile;
Settings::getInstance().setCurrentProfile(profile); Settings::getInstance().switchProfile(profile);
} }
else else
qWarning() << "Warning: -P profile" << profile + ".tox" << "doesn't exist"; qWarning() << "Warning: -P profile" << profile + ".tox" << "doesn't exist";

View File

@ -24,28 +24,36 @@
#include <QDebug> #include <QDebug>
#include <QSqlError> #include <QSqlError>
qint64 EncryptedDb::plainChunkSize = 4096; qint64 EncryptedDb::encryptedChunkSize = 4096;
qint64 EncryptedDb::encryptedChunkSize = EncryptedDb::plainChunkSize + tox_pass_encryption_extra_length(); qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - tox_pass_encryption_extra_length();
EncryptedDb::EncryptedDb(const QString &fname, QList<QString> initList) : EncryptedDb::EncryptedDb(const QString &fname, QList<QString> initList) :
PlainDb(":memory:", initList), encrFile(fname) PlainDb(":memory:", initList), fileName(fname)
{ {
QByteArray fileContent; QByteArray fileContent;
if (pullFileContent()) if (pullFileContent(fileName, buffer))
{ {
chunkPosition = encrFile.size() / encryptedChunkSize; qDebug() << "EncryptedDb: writing old data";
encrFile.setFileName(fileName);
encrFile.seek(0); encrFile.open(QIODevice::ReadOnly);
fileContent = encrFile.readAll(); fileContent = encrFile.readAll();
} else { chunkPosition = encrFile.size() / encryptedChunkSize;
qWarning() << "corrupted history log file will be wiped!"; encrFile.close();
chunkPosition = 0;
} }
else
chunkPosition = 0;
encrFile.close(); encrFile.setFileName(fileName);
encrFile.open(QIODevice::WriteOnly);
encrFile.write(fileContent); if (!encrFile.open(QIODevice::WriteOnly))
encrFile.flush(); {
qWarning() << "can't open file:" << fileName;
}
else
{
encrFile.write(fileContent);
encrFile.flush();
}
} }
EncryptedDb::~EncryptedDb() EncryptedDb::~EncryptedDb()
@ -57,27 +65,34 @@ EncryptedDb::~EncryptedDb()
QSqlQuery EncryptedDb::exec(const QString &query) QSqlQuery EncryptedDb::exec(const QString &query)
{ {
QSqlQuery retQSqlQuery = PlainDb::exec(query); QSqlQuery retQSqlQuery = PlainDb::exec(query);
if (query.startsWith("INSERT", Qt::CaseInsensitive)) if (checkCmd(query))
appendToEncrypted(query); appendToEncrypted(query);
return retQSqlQuery; 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; QByteArray fileContent;
while (!encrFile.atEnd()) while (!dbFile.atEnd())
{ {
QByteArray encrChunk = encrFile.read(encryptedChunkSize); QByteArray encrChunk = dbFile.read(encryptedChunkSize);
buffer = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); qDebug() << "EncryptedDb::pullFileContent: got chunk:" << encrChunk.size();
if (buffer.size() > 0) buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory);
if (buf.size() > 0)
{ {
fileContent += buffer; fileContent += buf;
} else { } else {
qWarning() << "Encrypted history log is corrupted: can't decrypt"; qWarning() << "EncryptedDb::pullFileContent: Encrypted history log is corrupted: can't decrypt, will be deleted";
buffer = QByteArray(); buf = QByteArray();
return false; return false;
} }
} }
@ -88,31 +103,17 @@ bool EncryptedDb::pullFileContent()
for (auto ba_line : splittedBA) for (auto ba_line : splittedBA)
{ {
QString line = QByteArray::fromBase64(ba_line); QString line = QByteArray::fromBase64(ba_line);
if (line.size() == 0) sqlCmds.append(line);
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;
}
} }
PlainDb::exec("BEGIN TRANSACTION;");
for (auto line : sqlCmds) for (auto line : sqlCmds)
{ {
QSqlQuery r = PlainDb::exec(line); QSqlQuery r = PlainDb::exec(line);
} }
PlainDb::exec("COMMIT TRANSACTION;");
dbFile.close();
return true; return true;
} }
@ -133,6 +134,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql)
if (encr.size() > 0) if (encr.size() > 0)
{ {
encrFile.write(encr); encrFile.write(encr);
encrFile.flush();
} }
buffer = buffer.right(buffer.size() - plainChunkSize); buffer = buffer.right(buffer.size() - plainChunkSize);
@ -170,3 +172,14 @@ bool EncryptedDb::check(const QString &fname)
file.close(); file.close();
return state; 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); static bool check(const QString &fname);
private: private:
bool pullFileContent(); bool pullFileContent(const QString& fname, QByteArray &buf);
void appendToEncrypted(const QString &sql); void appendToEncrypted(const QString &sql);
bool checkCmd(const QString &cmd);
QFile encrFile; QFile encrFile;
QString fileName;
static qint64 plainChunkSize; static qint64 plainChunkSize;
static qint64 encryptedChunkSize; static qint64 encryptedChunkSize;

View File

@ -63,6 +63,13 @@ void Settings::resetInstance()
} }
} }
void Settings::switchProfile(const QString& profile)
{
setCurrentProfile(profile);
save(false);
resetInstance();
}
void Settings::load() void Settings::load()
{ {
if (loaded) if (loaded)
@ -191,13 +198,6 @@ void Settings::load()
splitterState = s.value("splitterState", QByteArray()).toByteArray(); splitterState = s.value("splitterState", QByteArray()).toByteArray();
s.endGroup(); 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"); s.beginGroup("Audio");
inDev = s.value("inDev", "").toString(); inDev = s.value("inDev", "").toString();
outDev = s.value("outDev", "").toString(); outDev = s.value("outDev", "").toString();
@ -230,38 +230,45 @@ void Settings::load()
loaded = true; loaded = true;
if (currentProfile.isEmpty()) // new profile in Core::switchConfiguration if (!currentProfile.isEmpty()) // new profile in Core::switchConfiguration
return; {
// 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 QSettings ps(filePath, QSettings::IniFormat);
QString tmp = dir.filePath(currentProfile + ".ini"); friendLst.clear();
if (QFile(tmp).exists()) ps.beginGroup("Friends");
filePath = tmp; 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); ps.beginGroup("Privacy");
friendLst.clear(); typingNotification = ps.value("typingNotification", false).toBool();
fs.beginGroup("Friends"); enableLogging = ps.value("enableLogging", false).toBool();
int size = fs.beginReadArray("Friend"); encryptLogs = ps.value("encryptLogs", false).toBool();
for (int i = 0; i < size; i ++) encryptTox = ps.value("encryptTox", false).toBool();
{ ps.endGroup();
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();
} }
void Settings::save(bool writeFriends) void Settings::save(bool writePersonal)
{ {
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); 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; qDebug() << "Settings: Saving in "<<path;
@ -341,13 +348,6 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("splitterState", splitterState); s.setValue("splitterState", splitterState);
s.endGroup(); 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.beginGroup("Audio");
s.setValue("inDev", inDev); s.setValue("inDev", inDev);
s.setValue("outDev", outDev); s.setValue("outDev", outDev);
@ -358,23 +358,30 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("camVideoRes",camVideoRes); s.setValue("camVideoRes",camVideoRes);
s.endGroup(); s.endGroup();
if (!writeFriends || currentProfile.isEmpty()) // Core::switchConfiguration if (writePersonal && !currentProfile.isEmpty()) // Core::switchConfiguration
return; {
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); ps.beginGroup("Privacy");
fs.beginGroup("Friends"); ps.setValue("typingNotification", typingNotification);
fs.beginWriteArray("Friend", friendLst.size()); ps.setValue("enableLogging", enableLogging);
int index = 0; ps.setValue("encryptLogs", encryptLogs);
for (auto& frnd : friendLst) ps.setValue("encryptTox", encryptTox);
{ ps.endGroup();
fs.setArrayIndex(index); }
fs.setValue("addr", frnd.addr);
fs.setValue("alias", frnd.alias);
fs.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
fs.endArray();
fs.endGroup();
} }
QString Settings::getSettingsDirPath() QString Settings::getSettingsDirPath()

View File

@ -32,6 +32,7 @@ class Settings : public QObject
public: public:
static Settings& getInstance(); static Settings& getInstance();
static void resetInstance(); static void resetInstance();
void switchProfile(const QString& profile);
~Settings() = default; ~Settings() = default;
void executeSettingsDialog(QWidget* parent); void executeSettingsDialog(QWidget* parent);
@ -236,8 +237,8 @@ public:
void setCompactLayout(bool compact); void setCompactLayout(bool compact);
public: public:
void save(bool writeFriends = true); void save(bool writePersonal = true);
void save(QString path, bool writeFriends = true); void save(QString path, bool writePersonal = true);
void load(); void load();
private: 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 "ui_setpassworddialog.h"
#include <QPushButton> #include <QPushButton>
SetPasswordDialog::SetPasswordDialog(QWidget *parent) : const double SetPasswordDialog::reasonablePasswordLength = 8.;
QDialog(parent),
ui(new Ui::SetPasswordDialog) SetPasswordDialog::SetPasswordDialog(QString body, QString extraButton, QWidget* parent)
: QDialog(parent)
, ui(new Ui::SetPasswordDialog)
, body(body+"\n")
{ {
ui->setupUi(this); 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())); 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() SetPasswordDialog::~SetPasswordDialog()
@ -35,10 +48,53 @@ SetPasswordDialog::~SetPasswordDialog()
void SetPasswordDialog::onPasswordEdit() 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->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
ui->body->setText(body);
}
else else
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 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() QString SetPasswordDialog::getPassword()

View File

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

View File

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

View File

@ -29,7 +29,6 @@
#include <QClipboard> #include <QClipboard>
#include <QInputDialog> #include <QInputDialog>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox>
IdentityForm::IdentityForm() : IdentityForm::IdentityForm() :
GenericForm(tr("Identity"), QPixmap(":/img/settings/identity.png")) GenericForm(tr("Identity"), QPixmap(":/img/settings/identity.png"))
@ -152,10 +151,11 @@ void IdentityForm::onRenameClicked()
name = Core::sanitize(name); name = Core::sanitize(name);
QDir dir(Settings::getSettingsDirPath()); QDir dir(Settings::getSettingsDirPath());
QString file = dir.filePath(name+Core::TOX_EXT); 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))) 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+Core::TOX_EXT), file);
QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini"));
bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name); bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name);
HistoryKeeper::renameHistory(cur, name); HistoryKeeper::renameHistory(cur, name);
Settings::getInstance().setCurrentProfile(name); Settings::getInstance().setCurrentProfile(name);
@ -176,8 +176,6 @@ void IdentityForm::onExportClicked()
if (QFile::exists(path)) if (QFile::exists(path))
{ {
// should we popup a warning? // 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); success = QFile::remove(path);
if (!success) if (!success)
{ {
@ -199,8 +197,8 @@ void IdentityForm::onDeleteClicked()
} }
else else
{ {
if (checkContinue(tr("Deletion imminent!","deletion confirmation title"), if (Widget::getInstance()->askQuestion(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"))) tr("Are you sure you want to delete this profile?","deletion confirmation text")))
{ {
QString profile = bodyUI->profiles->currentText(); QString profile = bodyUI->profiles->currentText();
QDir dir(Settings::getSettingsDirPath()); QDir dir(Settings::getSettingsDirPath());
@ -238,7 +236,7 @@ void IdentityForm::onImportClicked()
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); 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))) tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
return; return;
@ -251,12 +249,6 @@ void IdentityForm::onNewClicked()
emit Widget::getInstance()->changeProfile(QString()); 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() void IdentityForm::disableSwitching()
{ {
bodyUI->loadButton->setEnabled(false); bodyUI->loadButton->setEnabled(false);

View File

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

View File

@ -23,6 +23,8 @@
#include "src/widget/widget.h" #include "src/widget/widget.h"
#include "src/widget/form/setpassworddialog.h" #include "src/widget/form/setpassworddialog.h"
#include <QMessageBox> #include <QMessageBox>
#include <QFile>
#include <QDebug>
PrivacyForm::PrivacyForm() : PrivacyForm::PrivacyForm() :
GenericForm(tr("Privacy"), QPixmap(":/img/settings/privacy.png")) GenericForm(tr("Privacy"), QPixmap(":/img/settings/privacy.png"))
@ -30,10 +32,15 @@ PrivacyForm::PrivacyForm() :
bodyUI = new Ui::PrivacySettings; bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this); bodyUI->setupUi(this);
bodyUI->encryptToxHLayout->addStretch();
bodyUI->encryptLogsHLayout->addStretch();
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated())); connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated())); connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated()));
connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated())); 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->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated()));
connect(bodyUI->changeToxPwButton, &QPushButton::clicked, this, &PrivacyForm::setToxPassword);
connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam()));
connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam()));
connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit())); connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit()));
@ -48,7 +55,7 @@ void PrivacyForm::onEnableLoggingUpdated()
{ {
Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked());
bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked()); bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked());
HistoryKeeper::getInstance()->resetInstance(); HistoryKeeper::resetInstance();
Widget::getInstance()->clearAllReceipts(); Widget::getInstance()->clearAllReceipts();
} }
@ -57,79 +64,172 @@ void PrivacyForm::onTypingNotificationEnabledUpdated()
Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked()); 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<HistoryKeeper::HistMessage> 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() 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 (setChatLogsPassword())
if (dialog.exec())
{ {
QString pswd = dialog.getPassword(); bodyUI->cbEncryptHistory->setChecked(true);
if (pswd.size() == 0) bodyUI->changeLogsPwButton->setEnabled(true);
encrytionState = false; return;
Core::getInstance()->setPassword(pswd, Core::ptHistory);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptHistory);
} }
} }
} }
else
Settings::getInstance().setEncryptLogs(encrytionState);
if (encrytionState && !HistoryKeeper::checkPassword())
{ {
if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted log"), QMessageBox::StandardButton button = QMessageBox::warning(
tr("You already have history log file encrypted with different password\nDo you want to delete old history file?"), Widget::getInstance(),
QMessageBox::Ok | QMessageBox::Cancel)) 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 QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile(true);
encrytionState = false; 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); core->clearPassword(Core::ptHistory);
bodyUI->cbEncryptHistory->setChecked(encrytionState); Settings::getInstance().setEncryptLogs(false);
bodyUI->cbEncryptHistory->setChecked(false);
bodyUI->changeLogsPwButton->setEnabled(false);
HistoryKeeper::resetInstance();
}
if (encrytionState) bool PrivacyForm::setToxPassword()
HistoryKeeper::resetInstance(); {
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()) if (int r = dialog->exec())
Core::getInstance()->clearPassword(Core::ptHistory); {
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() 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 (setToxPassword())
if (dialog.exec())
{ {
QString pswd = dialog.getPassword(); bodyUI->cbEncryptTox->setChecked(true);
if (pswd.size() == 0) bodyUI->changeToxPwButton->setEnabled(true);
encrytionState = false; return;
Core::getInstance()->setPassword(pswd, Core::ptMain);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptMain);
} }
} }
} }
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); bodyUI->cbEncryptTox->setChecked(false);
Settings::getInstance().setEncryptTox(encrytionState); Settings::getInstance().setEncryptTox(false);
bodyUI->changeToxPwButton->setEnabled(false);
if (!Settings::getInstance().getEncryptTox()) core->clearPassword(Core::ptMain);
Core::getInstance()->clearPassword(Core::ptMain);
} }
void PrivacyForm::setNospam() void PrivacyForm::setNospam()
@ -148,8 +248,10 @@ void PrivacyForm::present()
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs()); bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->changeLogsPwButton->setEnabled(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging()); bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox()); bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
bodyUI->changeToxPwButton->setEnabled(Settings::getInstance().getEncryptTox());
} }
void PrivacyForm::generateRandomNospam() void PrivacyForm::generateRandomNospam()

View File

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

View File

@ -57,7 +57,7 @@
<string extracomment="History keeping still under developing. Log format changin is possible."/> <string extracomment="History keeping still under developing. Log format changin is possible."/>
</property> </property>
<property name="text"> <property name="text">
<string>Keep History (unstable)</string> <string>Keep chat logs (mostly stable)</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -67,31 +67,63 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="title"> <property name="title">
<string>Encryption</string> <string>Local file encryption</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QCheckBox" name="cbEncryptTox"> <widget class="QLabel" name="encryptStatement">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <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> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="cbEncryptHistory"> <layout class="QHBoxLayout" name="encryptToxHLayout">
<property name="enabled"> <item>
<bool>false</bool> <widget class="QCheckBox" name="cbEncryptTox">
</property> <property name="enabled">
<property name="text"> <bool>true</bool>
<string>Encrypt History</string> </property>
</property> <property name="text">
<property name="checkable"> <string>Encrypt Tox data file</string>
<bool>false</bool> </property>
</property> </widget>
</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> </item>
</layout> </layout>
</widget> </widget>

View File

@ -30,12 +30,6 @@ void toxSaveEventHandler(const QByteArray& eventData)
handleToxSave(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) void handleToxSave(const QString& path)
{ {
Core* core = Core::getInstance(); Core* core = Core::getInstance();
@ -67,7 +61,7 @@ void handleToxSave(const QString& path)
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); 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))) QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
return; return;

View File

@ -32,7 +32,6 @@
#include "form/chatform.h" #include "form/chatform.h"
#include "maskablepixmapwidget.h" #include "maskablepixmapwidget.h"
#include "src/historykeeper.h" #include "src/historykeeper.h"
#include "form/inputpassworddialog.h"
#include "src/autoupdate.h" #include "src/autoupdate.h"
#include "src/audio.h" #include "src/audio.h"
#include "src/platform/timer.h" #include "src/platform/timer.h"
@ -87,7 +86,7 @@ void Widget::init()
icon = new QSystemTrayIcon(this); icon = new QSystemTrayIcon(this);
updateTrayIcon(); updateTrayIcon();
trayMenu = new QMenu; trayMenu = new QMenu;
statusOnline = new QAction(tr("Online"), this); statusOnline = new QAction(tr("Online"), this);
statusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png")); statusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png"));
connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline())); connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline()));
@ -99,19 +98,19 @@ void Widget::init()
connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy())); connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy()));
actionQuit = new QAction(tr("&Quit"), this); actionQuit = new QAction(tr("&Quit"), this);
connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
trayMenu->addAction(statusOnline); trayMenu->addAction(statusOnline);
trayMenu->addAction(statusAway); trayMenu->addAction(statusAway);
trayMenu->addAction(statusBusy); trayMenu->addAction(statusBusy);
trayMenu->addSeparator(); trayMenu->addSeparator();
trayMenu->addAction(actionQuit); trayMenu->addAction(actionQuit);
icon->setContextMenu(trayMenu); icon->setContextMenu(trayMenu);
connect(icon, connect(icon,
SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, this,
SLOT(onIconClick(QSystemTrayIcon::ActivationReason))); SLOT(onIconClick(QSystemTrayIcon::ActivationReason)));
if (Settings::getInstance().getShowSystemTray()) if (Settings::getInstance().getShowSystemTray())
{ {
icon->show(); icon->show();
@ -241,7 +240,6 @@ void Widget::init()
connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, this, &Widget::playRingtone); connect(core, &Core::avInvite, this, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection); 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, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int))); 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(); QString path, profile = Settings::getInstance().getCurrentProfile();
path = dir.filePath(profile + Core::TOX_EXT); path = dir.filePath(profile + Core::TOX_EXT);
QFile file(path); QFile file(path);
if (profile == "" || !file.exists()) if (profile.isEmpty() || !file.exists())
{ {
Settings::getInstance().setCurrentProfile(""); Settings::getInstance().setCurrentProfile("");
#if 1 // deprecation attempt #if 1 // deprecation attempt
@ -402,10 +400,13 @@ QString Widget::detectProfile()
#endif #endif
{ {
profile = askProfiles(); profile = askProfiles();
if (profile != "") if (profile.isEmpty())
return dir.filePath(profile + Core::TOX_EXT);
else
return ""; return "";
else
{
Settings::getInstance().switchProfile(profile);
return dir.filePath(profile + Core::TOX_EXT);
}
} }
} }
else 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()); 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) void Widget::onFriendTypingChanged(int friendId, bool isTyping)
{ {
Friend* f = FriendList::findFriend(friendId); Friend* f = FriendList::findFriend(friendId);
@ -1220,21 +1207,17 @@ void Widget::onSplitterMoved(int pos, int index)
saveSplitterGeometry(); 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 // We can only display widgets from the GUI thread
if (QThread::currentThread() != qApp->thread()) if (QThread::currentThread() != qApp->thread())
{ {
QMessageBox::StandardButton ret;
QMetaObject::invokeMethod(this, "showWarningMsgBox", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "showWarningMsgBox", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QMessageBox::StandardButton, ret), Q_ARG(const QString&, title), Q_ARG(const QString&, msg));
Q_ARG(const QString&, title), Q_ARG(const QString&, msg),
Q_ARG(QMessageBox::StandardButtons, buttons));
return ret;
} }
else 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 // We can only display widgets from the GUI thread
if (QThread::currentThread() != qApp->thread()) if (QThread::currentThread() != qApp->thread())
{ {
bool ret; bool ret;
QMetaObject::invokeMethod(this, "askMsgboxQuestion", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "askQuestion", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret), 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; return ret;
} }
else 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."));
}
} }
} }

View File

@ -64,10 +64,12 @@ public:
void clearContactsList(); void clearContactsList();
void setTranslation(); void setTranslation();
void updateTrayIcon(); void updateTrayIcon();
Q_INVOKABLE QMessageBox::StandardButton showWarningMsgBox(const QString& title, const QString& msg, Q_INVOKABLE void showWarningMsgBox(const QString& title, const QString& msg);
QMessageBox::StandardButtons buttonss = QMessageBox::Ok);
Q_INVOKABLE void setEnabledThreadsafe(bool enabled); 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(); ~Widget();
virtual void closeEvent(QCloseEvent *event); virtual void closeEvent(QCloseEvent *event);
@ -133,7 +135,6 @@ private slots:
void playRingtone(); void playRingtone();
void onIconClick(QSystemTrayIcon::ActivationReason); void onIconClick(QSystemTrayIcon::ActivationReason);
void onUserAwayCheck(); void onUserAwayCheck();
void getPassword(QString info, int passtype, uint8_t* salt);
void onFriendTypingChanged(int friendId, bool isTyping); void onFriendTypingChanged(int friendId, bool isTyping);
void onSetShowSystemTray(bool newValue); void onSetShowSystemTray(bool newValue);
void onSplitterMoved(int pos, int index); void onSplitterMoved(int pos, int index);
@ -147,7 +148,6 @@ private:
void removeGroup(Group* g, bool fake = false); void removeGroup(Group* g, bool fake = false);
void saveWindowGeometry(); void saveWindowGeometry();
void saveSplitterGeometry(); void saveSplitterGeometry();
QString askProfiles();
QString detectProfile(); QString detectProfile();
QSystemTrayIcon *icon; QSystemTrayIcon *icon;
QMenu *trayMenu; QMenu *trayMenu;