Compare commits

...

4 Commits

10 changed files with 313 additions and 119 deletions

View File

@ -3,7 +3,8 @@
Client::Client(const QString &jid, QObject *const parent) :
QXmppClient(parent),
jid(jid),
trust_db(jid),
db(jid),
trust_db(jid, db),
trust(&trust_db),
omemo_db(jid),
omemo(&omemo_db)
@ -18,3 +19,13 @@ QString Client::jidBare()
{
return configuration().jidBare();
}
const JidDb &Client::database() const
{
return db;
}
JidDb &Client::database()
{
return db;
}

View File

@ -2,6 +2,7 @@
#define CLIENT_H
#include "omemo_db.h"
#include "jiddb.h"
#include "trust_db.h"
#include <QObject>
#include <QString>
@ -17,9 +18,12 @@ class Client : public QXmppClient
public:
Client(const QString &jid, QObject *parent = nullptr);
QString jidBare();
const JidDb &database() const;
JidDb &database();
private:
const QString jid;
JidDb db;
TrustDb trust_db;
QXmppTrustManager trust;
QXmppPubSubManager pubsub;

View File

@ -3,16 +3,16 @@
#include <QListWidgetItem>
#include <QScroller>
Contacts::Contacts(const QHash<QString, JidDb *> &databases,
Contacts::Contacts(const QList<Client *> &clients,
QWidget *const parent) :
QDialog(parent)
{
ui.setupUi(this);
QScroller::grabGesture(ui.contacts_list, QScroller::TouchGesture);
for (const auto db : databases)
for (const auto &contact : db->roster())
add(db->jid, contact);
for (const auto c : clients)
for (const auto &contact : c->database().roster())
add(c->jidBare(), contact);
connect(ui.contacts_list, &QListWidget::itemActivated, this,
[this]

View File

@ -2,7 +2,7 @@
#define CONTACTS_H
#include "ui_contacts.h"
#include "jiddb.h"
#include "client.h"
#include <QDialog>
#include <QHash>
#include <QString>
@ -13,7 +13,7 @@ class Contacts : public QDialog
Q_OBJECT
public:
Contacts(const QHash<QString, JidDb *> &databases,
Contacts(const QList<Client *> &clients,
QWidget *parent = nullptr);
void add(const QString &own, const QString &other);

View File

@ -84,7 +84,7 @@ int JidDb::ensureContactDb(const QString &jid) const
return 0;
}
QList<JidDb::Message> JidDb::getMessages(const QString &jid,
QList<JidDb::Message> JidDb::messages(const QString &jid,
const int tail) const
{
QSqlQuery q(db);
@ -95,14 +95,14 @@ QList<JidDb::Message> JidDb::getMessages(const QString &jid,
{
if (!q.exec("select * from '" + jid + "' order by time;"))
{
std::cerr << "JidDb::getMessages: query exec failed";
std::cerr << "JidDb::messages: query exec failed";
return QList<Message>();
}
}
else if (!q.exec("select * from '" + jid + "' order by time desc limit "
+ QString::number(tail) + ";"))
{
std::cerr << "JidDb::getMessages: query exec failed" << std::endl;
std::cerr << "JidDb::messages: query exec failed" << std::endl;
return QList<Message>();
}
@ -116,7 +116,7 @@ QList<JidDb::Message> JidDb::getMessages(const QString &jid,
if (!ok)
{
std::cerr << "JidDb::getMessages: invalid time" << std::endl;
std::cerr << "JidDb::messages: invalid time" << std::endl;
// Attempt to read other messages.
continue;
}
@ -132,7 +132,7 @@ QList<JidDb::Message> JidDb::getMessages(const QString &jid,
m.direction = Direction::Out;
else
{
std::cerr << "JidDb::getMessages: invalid direction" << std::endl;
std::cerr << "JidDb::messages: invalid direction" << std::endl;
// Attempt to read other messages.
continue;
}
@ -181,7 +181,7 @@ QStringList JidDb::tables() const
if (!q.exec("select name from sqlite_schema where "
"type = 'table' and name not like 'sqlite_%'"))
{
std::cerr << "JidDb::getConversations: query failed" << std::endl;
std::cerr << "JidDb::conversations: query failed" << std::endl;
return QStringList();
}
@ -193,7 +193,7 @@ QStringList JidDb::tables() const
return ret;
}
QList<JidDb::Conversation> JidDb::getConversations() const
QList<JidDb::Conversation> JidDb::conversations() const
{
const auto conversations = tables();
QList<Conversation> ret;
@ -201,11 +201,11 @@ QList<JidDb::Conversation> JidDb::getConversations() const
for (const auto &jid : conversations)
if (jid.contains('@'))
{
const auto messages = getMessages(jid, 1);
const auto msgs = messages(jid, 1);
if (!messages.isEmpty())
if (!msgs.isEmpty())
{
const auto &m = messages.first();
const auto &m = msgs.first();
ret << Conversation(jid, m.body, m.dt);
}

32
jiddb.h
View File

@ -2,12 +2,15 @@
#define JID_DB_H
#include "direction.h"
#include <QByteArray>
#include <QDateTime>
#include <QList>
#include <QObject>
#include <QPair>
#include <QString>
#include <QStringList>
#include <QSqlDatabase>
#include <QVariant>
class JidDb : public QObject
{
@ -30,17 +33,40 @@ public:
QDateTime dt;
};
struct Trust
{
QString security_policy;
};
struct Keys
{
Keys(const QString &owner, const QString &trust_level,
const QList<QByteArray> &keys) :
owner(owner),
trust_level(trust_level),
keys(keys)
{}
QString owner, trust_level;
QList<QByteArray> keys;
};
JidDb(const QString &jid);
QStringList roster() const;
const QString jid;
const QString &jid;
public Q_SLOTS:
QList<Conversation> getConversations() const;
QList<Message> getMessages(const QString &jid,
QList<Conversation> conversations() const;
QList<Message> messages(const QString &jid,
int tail = -1) const;
QString securityPolicy(const QString &encryption) const;
QList<Keys> keys(const QString &encryption) const;
int storeMessage(const Message &msg) const;
int addToRoster(const QString &jid);
int addToRoster(const QStringList &roster);
int storeSecurityPolicy(const QString &encryption,
const QString &policy) const;
int storeKeys(const QString &encryption, const Keys &keys) const;
void removeKeys(void) const;
Q_SIGNALS:
void addedToRoster(QString jid);

View File

@ -1,4 +1,6 @@
#include "trust_db.h"
#include <QXmppTrustLevel.h>
#include <QXmppTrustSecurityPolicy.h>
#include <QXmppConfiguration.h>
#include <QXmppFutureUtils_p.h>
#include <QXmppPromise.h>
@ -7,21 +9,50 @@
#include <QtConcurrent>
#include <iostream>
TrustDb::TrustDb(const QString &jid) :
jid(jid)
TrustDb::TrustDb(const QString &jid, const JidDb &db) :
jid(jid),
db(db)
{
}
static const struct
{
QXmpp::TrustLevel level;
const char *str;
} trustlevels[] =
{
{QXmpp::TrustLevel::Undecided, "Undecided"},
{QXmpp::TrustLevel::AutomaticallyDistrusted, "AutomaticallyDistrusted"},
{QXmpp::TrustLevel::ManuallyDistrusted, "ManuallyDistrusted"},
{QXmpp::TrustLevel::AutomaticallyTrusted, "AutomaticallyTrusted"},
{QXmpp::TrustLevel::ManuallyTrusted, "ManuallyTrusted"},
{QXmpp::TrustLevel::Authenticated, "Authenticated"}
};
static const struct
{
QXmpp::TrustSecurityPolicy policy;
const char *str;
} tsp_levels[] =
{
{QXmpp::NoSecurityPolicy, "NoSecurityPolicy"},
{QXmpp::Toakafa, "Toakafa"}
};
static QString toString(const QXmpp::TrustSecurityPolicy securityPolicy)
{
switch (securityPolicy)
{
case QXmpp::NoSecurityPolicy:
return "NoSecurityPolicy";
for (const auto &t : tsp_levels)
if (t.policy == securityPolicy)
return t.str;
case QXmpp::Toakafa:
return "Toakafa";
}
return "unknown";
}
static QString toString(const QXmpp::TrustLevel &trustLevel)
{
for (const auto &t : trustlevels)
if (t.level == trustLevel)
return t.str;
return "unknown";
}
@ -29,16 +60,24 @@ static QString toString(const QXmpp::TrustSecurityPolicy securityPolicy)
static int toSecurityPolicy(const QString &s,
QXmpp::TrustSecurityPolicy &securityPolicy)
{
if (s == "NoSecurityPolicy")
{
securityPolicy = QXmpp::NoSecurityPolicy;
return 0;
}
else if (s == "Toakafa")
{
securityPolicy = QXmpp::Toakafa;
return 0;
}
for (const auto &t : tsp_levels)
if (t.str == s)
{
securityPolicy = t.policy;
return 0;
}
return -1;
}
static int toTrustLevel(const QString &tstr, QXmpp::TrustLevel &trustLevel)
{
for (const auto &t : trustlevels)
if (t.str == tstr)
{
trustLevel = t.level;
return 0;
}
return -1;
}
@ -51,35 +90,18 @@ QString TrustDb::service() const
QXmppTask<void> TrustDb::setSecurityPolicy(const QString &encryption,
const QXmpp::TrustSecurityPolicy securityPolicy)
{
QKeychain::WritePasswordJob job(service());
QEventLoop loop;
job.setKey("securityPolicy/" + encryption);
job.setTextData(toString(securityPolicy));
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to store security policy: "
<< qPrintable(job.errorString()) << std::endl;
if (db.storeSecurityPolicy(encryption, toString(securityPolicy)))
std::cerr << "TrustDb::setSecurityPolicy: storeSecurityPolicy failed"
<< std::endl;
return QXmpp::Private::makeReadyTask();
}
QXmppTask<void> TrustDb::resetSecurityPolicy(const QString &encryption)
{
QKeychain::DeletePasswordJob job(service());
QEventLoop loop;
job.setKey("securityPolicy/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to reset security policy: "
<< qPrintable(job.errorString()) << std::endl;
if (db.storeSecurityPolicy(encryption, QString()))
std::cerr << "TrustDb::resetSecurityPolicy: storeSecurityPolicy failed"
<< std::endl;
return QXmpp::Private::makeReadyTask();
}
@ -87,23 +109,11 @@ QXmppTask<void> TrustDb::resetSecurityPolicy(const QString &encryption)
QXmppTask<QXmpp::TrustSecurityPolicy> TrustDb::securityPolicy(
const QString &encryption)
{
QKeychain::ReadPasswordJob job(service());
QEventLoop loop;
const auto s = db.securityPolicy(encryption);
QXmpp::TrustSecurityPolicy policy;
job.setKey("securityPolicy/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to read security policy: "
<< qPrintable(job.errorString()) << std::endl;
QXmpp::TrustSecurityPolicy policy = QXmpp::Toakafa;
const QString data = job.textData();
if (toSecurityPolicy(data, policy))
std::cerr << "Invalid security policy " << qPrintable(data)
if (toSecurityPolicy(s, policy))
std::cerr << "TrustDb::securityPolicy: toSecurityPolicy failed"
<< std::endl;
return QXmpp::Private::makeReadyTask(QXmpp::TrustSecurityPolicy(policy));
@ -170,23 +180,60 @@ QXmppTask<void> TrustDb::addKeys(const QString &encryption,
const QString &keyOwnerJid, const QList<QByteArray> &keyIds,
const QXmpp::TrustLevel trustLevel)
{
const struct JidDb::Keys keys(keyOwnerJid, toString(trustLevel), keyIds);
if (db.storeKeys(encryption, keys))
std::cerr << "TrustDb::addKeys: storeKeys failed" << std::endl;
return QXmpp::Private::makeReadyTask();
}
QXmppTask<void> TrustDb::removeKeys(const QString &encryption,
const QList<QByteArray> &keyIds)
{
auto keys = db.keys(encryption);
for (const auto &id : keyIds)
for (auto &k : keys)
{
const auto i = k.keys.indexOf(id);
if (i != -1)
{
k.keys.removeAt(i);
break;
}
}
for (const auto &k : keys)
if (db.storeKeys(encryption, k))
std::cerr << "TrustDb::removeKeys: storeKeys failed" << std::endl;
return QXmpp::Private::makeReadyTask();
}
QXmppTask<void> TrustDb::removeKeys(const QString &encryption,
const QString &keyOwnerJid)
{
auto keys = db.keys(encryption);
for (int i = 0; i < keys.count(); i++)
if (keys[i].owner == keyOwnerJid)
{
keys.removeAt(i);
break;
}
for (const auto &k : keys)
if (db.storeKeys(encryption, k))
std::cerr << "TrustDb::removeKeys: storeKeys failed" << std::endl;
return QXmpp::Private::makeReadyTask();
}
QXmppTask<void> TrustDb::removeKeys(const QString &encryption)
{
db.removeKeys();
return QXmpp::Private::makeReadyTask();
}
@ -194,8 +241,32 @@ QXmppTask<QHash<QXmpp::TrustLevel,
QMultiHash<QString, QByteArray>>> TrustDb::keys(const QString &encryption,
const QXmpp::TrustLevels trustLevels)
{
QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>> ret;
const auto keys = db.keys(encryption);
for (const auto &k : keys)
{
QXmpp::TrustLevel level;
if (toTrustLevel(k.trust_level, level))
{
std::cerr << "TrustDb::keys: invalid trust level: "
<< qPrintable(k.trust_level) << std::endl;
continue;
}
else if (!trustLevels.testFlag(level))
continue;
QMultiHash<QString, QByteArray> mh;
for (const auto &key : k.keys)
mh.insert(k.owner, key);
ret.insert(level, mh);
}
return QXmpp::Private::makeReadyTask(QHash<QXmpp::TrustLevel,
QMultiHash<QString, QByteArray>>());
QMultiHash<QString, QByteArray>>(ret));
}
QXmppTask<QHash<QString,
@ -203,14 +274,66 @@ QXmppTask<QHash<QString,
const QList<QString> &keyOwnerJids,
const QXmpp::TrustLevels trustLevels)
{
QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>> ret;
const auto keys = db.keys(encryption);
for (const auto &jid : keyOwnerJids)
for (const auto &k : keys)
{
if (k.owner != jid)
continue;
QXmpp::TrustLevel level;
if (toTrustLevel(k.trust_level, level))
{
std::cerr << "TrustDb::keys: invalid trust level: "
<< qPrintable(k.trust_level) << std::endl;
continue;
}
else if (!trustLevels.testFlag(level))
continue;
QHash<QByteArray, QXmpp::TrustLevel> h;
for (const auto &key : k.keys)
h.insert(key, level);
ret.insert(jid, h);
break;
}
return QXmpp::Private::makeReadyTask(QHash<QString,
QHash<QByteArray, QXmpp::TrustLevel>>());
QHash<QByteArray, QXmpp::TrustLevel>>(ret));
}
QXmppTask<bool> TrustDb::hasKey(const QString &encryption,
const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels)
{
return QXmpp::Private::makeReadyTask(bool());
bool ret = false;
const auto keys = db.keys(encryption);
for (const auto &k : keys)
{
if (k.owner != keyOwnerJid)
continue;
QXmpp::TrustLevel level;
if (toTrustLevel(k.trust_level, level))
{
std::cerr << "TrustDb::keys: invalid trust level: "
<< qPrintable(k.trust_level) << std::endl;
break;
}
else if (!trustLevels.testFlag(level))
break;
ret = true;
break;
}
return QXmpp::Private::makeReadyTask(bool(ret));
}
QXmppTask<QHash<QString,
@ -219,8 +342,33 @@ QXmppTask<QHash<QString,
const QMultiHash<QString, QByteArray> &keyIds,
const QXmpp::TrustLevel trustLevel)
{
QHash<QString, QMultiHash<QString, QByteArray>> ret;
auto keys = db.keys(encryption);
const auto str = toString(trustLevel);
for (const auto &owner : keyIds.keys())
for (auto &k : keys)
{
if (k.owner != owner)
continue;
k.trust_level = str;
QMultiHash<QString, QByteArray> mh;
for (const auto &id : k.keys)
mh.insert(owner, id);
ret.insert(owner, mh);
break;
}
for (const auto &key : keys)
if (db.storeKeys(encryption, key))
std::cerr << "TrustDb::setTrustLevel: storeKeys failed"
<< std::endl;
return QXmpp::Private::makeReadyTask(QHash<QString,
QMultiHash<QString, QByteArray>>());
QMultiHash<QString, QByteArray>>(ret));
}
QXmppTask<QHash<QString,
@ -244,3 +392,13 @@ QXmppTask<void> TrustDb::resetAll(const QString &encryption)
{
return QXmpp::Private::makeReadyTask();
}
namespace QXmpp
{
uint qHash(const TrustLevel &key, uint seed)
{
return qHash(key, seed);
}
}

View File

@ -1,6 +1,7 @@
#ifndef TRUST_DB_H
#define TRUST_DB_H
#include "jiddb.h"
#include <QXmppClient.h>
#include <QXmppTask.h>
#include <QXmppTrustLevel.h>
@ -14,7 +15,7 @@
class TrustDb : virtual public QXmppTrustStorage
{
public:
TrustDb(const QString &jid);
TrustDb(const QString &jid, const JidDb &db);
QXmppTask<void> setSecurityPolicy(const QString &encryption,
QXmpp::TrustSecurityPolicy securityPolicy) override;
@ -64,6 +65,7 @@ public:
private:
const QString &jid;
const JidDb &db;
QString service() const;
};

View File

@ -23,7 +23,6 @@ xxcc::xxcc(QWidget *const parent) :
ui.setupUi(this);
QScroller::grabGesture(ui.conversations_list, QScroller::TouchGesture);
QScroller::grabGesture(ui.messages, QScroller::TouchGesture);
setupDatabases(pairs);
connectAccounts(pairs);
retrieveConversations();
connect(ui.accounts, &QPushButton::released, this,
@ -39,16 +38,20 @@ xxcc::xxcc(QWidget *const parent) :
connect(ui.contacts, &QPushButton::released, this,
[this]
{
Contacts c(databases, this);
Contacts c(clients, this);
connect(&c, &Contacts::startChat, this, &xxcc::startChat);
for (const auto db : databases)
for (const auto cl : clients)
{
const auto db = &cl->database();
c.connect(db, &JidDb::addedToRoster, &c,
[&c, db] (const QString jid)
{
c.add(db->jid, jid);
});
}
c.exec();
});
@ -74,11 +77,11 @@ xxcc::xxcc(QWidget *const parent) :
break;
}
if (databases.contains(selected->jidBare()))
if (selected)
{
const auto db = databases[selected->jidBare()];
const auto &db = selected->database();
static const auto n_messages = 20;
const auto messages = db->getMessages(conv->to, n_messages);
const auto messages = db.messages(conv->to, n_messages);
for (auto it = messages.rbegin(); it != messages.rend(); it++)
new Message(it->body, it->dt, it->direction, ui.messages);
@ -97,9 +100,6 @@ xxcc::~xxcc()
{
for (const auto c : clients)
delete c;
for (const auto db : databases)
delete db;
}
void xxcc::connectAccounts(const QList<Credentials::Pair> &pairs)
@ -120,12 +120,6 @@ void xxcc::connectAccounts(const QList<Credentials::Pair> &pairs)
}
}
void xxcc::setupDatabases(const QList<Credentials::Pair> &pairs)
{
for (const auto &p : pairs)
databases[p.first] = new JidDb(p.first);
}
void xxcc::startChat(const QString from, const QString to)
{
bool found = false;
@ -194,12 +188,7 @@ void xxcc::addAccount(Client *const c)
roster->connect(roster, &QXmppRosterManager::rosterReceived, c,
[this, c, roster]
{
if (databases.contains(c->jidBare()))
{
const auto db = databases[c->jidBare()];
db->addToRoster(roster->getRosterBareJids());
}
c->database().addToRoster(roster->getRosterBareJids());
});
else
throw std::runtime_error("Expected non-null QXmppRosterManager");
@ -239,23 +228,30 @@ void xxcc::storeMessage(const QXmppMessage &msg, const Direction dir) const
break;
}
if (databases.contains(jid))
for (const auto c : clients)
{
const auto db = databases[jid];
JidDb::Message m;
if (c->jidBare() == jid)
{
const auto &db = c->database();
JidDb::Message m;
m.body = msg.body();
m.dt = msg.stamp();
m.direction = dir;
m.contact = contact;
db->storeMessage(m);
m.body = msg.body();
m.dt = msg.stamp();
m.direction = dir;
m.contact = contact;
db.storeMessage(m);
}
}
}
void xxcc::retrieveConversations()
{
for (const auto db : databases)
for (const auto &conv : db->getConversations())
new Conversation(db->jid, conv.to,
ui.conversations_list, conv.last_msg, conv.dt);
for (const auto c : clients)
{
const auto &db = c->database();
for (const auto &conv : db.conversations())
new Conversation(db.jid, conv.to,
ui.conversations_list, conv.last_msg, conv.dt);
}
}

3
xxcc.h
View File

@ -6,7 +6,6 @@
#include "credentials.h"
#include "jiddb.h"
#include <QDateTime>
#include <QHash>
#include <QList>
#include <QWidget>
#include <QSqlDatabase>
@ -25,10 +24,8 @@ private:
enum Tab {Conversations, Chat};
Ui_main ui;
QList<Client *> clients;
QHash<QString, JidDb *> databases;
Credentials creds;
Client *selected;
void setupDatabases(const QList<Credentials::Pair> &pairs);
void connectAccounts(const QList<Credentials::Pair> &pairs);
void storeMessage(const QXmppMessage &msg, Direction dir) const;
void retrieveConversations();