Respond to MAM requests on a channel JID
At the moment, result-set-management is not implemented, the whole history (well, at most 1024 messages) is returned.
This commit is contained in:
@@ -51,7 +51,7 @@ RUN useradd tester -m
|
|||||||
RUN apt install -y automake autoconf flex bison libltdl-dev openssl
|
RUN apt install -y automake autoconf flex bison libltdl-dev openssl
|
||||||
RUN apt install -y libtool
|
RUN apt install -y libtool
|
||||||
RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis
|
RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis
|
||||||
RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install
|
RUN cd /charybdis && git checkout 4f2b9a4 && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install
|
||||||
RUN chown -R tester:tester /home/tester/ircd
|
RUN chown -R tester:tester /home/tester/ircd
|
||||||
RUN rm -rf /charybdis
|
RUN rm -rf /charybdis
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RUN useradd tester
|
|||||||
RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel
|
RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel
|
||||||
RUN dnf install -y libtool
|
RUN dnf install -y libtool
|
||||||
RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis
|
RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis
|
||||||
RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install
|
RUN cd /charybdis && git checkout 4f2b9a4 && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin --with-included-boost && make -j8 && make install
|
||||||
RUN chown -R tester:tester /home/tester/ircd
|
RUN chown -R tester:tester /home/tester/ircd
|
||||||
RUN rm -rf /charybdis
|
RUN rm -rf /charybdis
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#include <utils/time.hpp>
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
std::string to_string(const std::time_t& timestamp)
|
||||||
|
{
|
||||||
|
constexpr std::size_t stamp_size = 20;
|
||||||
|
char date_buf[stamp_size];
|
||||||
|
std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp));
|
||||||
|
return {std::begin(date_buf), std::end(date_buf)};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
std::string to_string(const std::time_t& timestamp);
|
||||||
|
}
|
||||||
@@ -26,7 +26,12 @@ public:
|
|||||||
}
|
}
|
||||||
std::string full() const
|
std::string full() const
|
||||||
{
|
{
|
||||||
return this->local + "@" + this->domain + "/" + this->resource;
|
std::string res = this->domain;
|
||||||
|
if (!this->local.empty())
|
||||||
|
res = this->local + "@" + this->domain;
|
||||||
|
if (!this->resource.empty())
|
||||||
|
res += "/" + this->resource;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,22 +7,10 @@
|
|||||||
#include <config/config.hpp>
|
#include <config/config.hpp>
|
||||||
#include <xmpp/jid.hpp>
|
#include <xmpp/jid.hpp>
|
||||||
#include <utils/sha1.hpp>
|
#include <utils/sha1.hpp>
|
||||||
|
#include <utils/time.hpp>
|
||||||
#include <stdexcept>
|
|
||||||
#include <iostream>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include <uuid.h>
|
#include <uuid.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include <louloulibs.h>
|
|
||||||
#ifdef SYSTEMD_FOUND
|
|
||||||
# include <systemd/sd-daemon.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
static std::set<std::string> kickable_errors{
|
static std::set<std::string> kickable_errors{
|
||||||
@@ -443,12 +431,9 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std:
|
|||||||
message.add_child(std::move(body));
|
message.add_child(std::move(body));
|
||||||
|
|
||||||
XmlNode delay("delay");
|
XmlNode delay("delay");
|
||||||
delay["xmlns"] = "urn:xmpp:delay";
|
delay["xmlns"] = DELAY_NS;
|
||||||
delay["from"] = muc_name + "@" + this->served_hostname;
|
delay["from"] = muc_name + "@" + this->served_hostname;
|
||||||
constexpr std::size_t stamp_size = 20;
|
delay["stamp"] = utils::to_string(timestamp);
|
||||||
char date_buf[stamp_size];
|
|
||||||
std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp));
|
|
||||||
delay["stamp"] = date_buf;
|
|
||||||
|
|
||||||
message.add_child(std::move(delay));
|
message.add_child(std::move(delay));
|
||||||
this->send_stanza(message);
|
this->send_stanza(message);
|
||||||
|
|||||||
@@ -26,6 +26,10 @@
|
|||||||
#define VERSION_NS "jabber:iq:version"
|
#define VERSION_NS "jabber:iq:version"
|
||||||
#define ADHOC_NS "http://jabber.org/protocol/commands"
|
#define ADHOC_NS "http://jabber.org/protocol/commands"
|
||||||
#define PING_NS "urn:xmpp:ping"
|
#define PING_NS "urn:xmpp:ping"
|
||||||
|
#define DELAY_NS "urn:xmpp:delay"
|
||||||
|
#define MAM_NS "urn:xmpp:mam:1"
|
||||||
|
#define FORWARD_NS "urn:xmpp:forward:0"
|
||||||
|
#define CLIENT_NS "jabber:client"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An XMPP component, communicating with an XMPP server using the protocole
|
* An XMPP component, communicating with an XMPP server using the protocole
|
||||||
|
|||||||
@@ -138,13 +138,13 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid,
|
|||||||
|
|
||||||
std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit)
|
std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit)
|
||||||
{
|
{
|
||||||
if (limit < 0)
|
if (limit == -1)
|
||||||
limit = 0;
|
limit = 1024;
|
||||||
auto res = litesql::select<db::MucLogLine>(*Database::db,
|
const auto& res = litesql::select<db::MucLogLine>(*Database::db,
|
||||||
db::MucLogLine::Owner == owner &&
|
db::MucLogLine::Owner == owner &&
|
||||||
db::MucLogLine::IrcChanName == chan_name &&
|
db::MucLogLine::IrcChanName == chan_name &&
|
||||||
db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Date, false).limit(limit).all();
|
db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Id, false).limit(limit).all();
|
||||||
return {res.rbegin(), res.rend()};
|
return {res.crbegin(), res.crend()};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::close()
|
void Database::close()
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
#include <xmpp/biboumi_adhoc_commands.hpp>
|
#include <xmpp/biboumi_adhoc_commands.hpp>
|
||||||
#include <bridge/list_element.hpp>
|
#include <bridge/list_element.hpp>
|
||||||
#include <config/config.hpp>
|
#include <config/config.hpp>
|
||||||
#include <xmpp/jid.hpp>
|
|
||||||
#include <utils/sha1.hpp>
|
#include <utils/sha1.hpp>
|
||||||
|
#include <utils/time.hpp>
|
||||||
|
#include <xmpp/jid.hpp>
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -25,6 +26,8 @@
|
|||||||
# include <systemd/sd-daemon.h>
|
# include <systemd/sd-daemon.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <database/database.hpp>
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
static std::set<std::string> kickable_errors{
|
static std::set<std::string> kickable_errors{
|
||||||
@@ -383,6 +386,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
|
|||||||
this->send_stanza(response);
|
this->send_stanza(response);
|
||||||
stanza_error.disable();
|
stanza_error.disable();
|
||||||
}
|
}
|
||||||
|
#ifdef USE_DATABASE
|
||||||
|
else if ((query = stanza.get_child("query", MAM_NS)))
|
||||||
|
{
|
||||||
|
if (this->handle_mam_request(stanza))
|
||||||
|
stanza_error.disable();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else if (type == "get")
|
else if (type == "get")
|
||||||
{
|
{
|
||||||
@@ -525,6 +535,73 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
|
|||||||
error_name = "feature-not-implemented";
|
error_name = "feature-not-implemented";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_DATABASE
|
||||||
|
bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
|
||||||
|
{
|
||||||
|
std::string id = stanza.get_tag("id");
|
||||||
|
Jid from(stanza.get_tag("from"));
|
||||||
|
Jid to(stanza.get_tag("to"));
|
||||||
|
|
||||||
|
const XmlNode* query = stanza.get_child("query", MAM_NS);
|
||||||
|
std::string query_id;
|
||||||
|
if (query)
|
||||||
|
query_id = query->get_tag("queryid");
|
||||||
|
|
||||||
|
Iid iid(to.local, {'#', '&'});
|
||||||
|
if (iid.type == Iid::Type::Channel && to.resource.empty())
|
||||||
|
{
|
||||||
|
const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1);
|
||||||
|
for (const db::MucLogLine& line: lines)
|
||||||
|
{
|
||||||
|
const auto queryid = query->get_tag("queryid");
|
||||||
|
if (!line.nick.value().empty())
|
||||||
|
this->send_archived_message(line, to.full(), from.full(), queryid);
|
||||||
|
}
|
||||||
|
this->send_iq_result_full_jid(id, from.full(), to.full());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
|
||||||
|
const std::string& queryid)
|
||||||
|
{
|
||||||
|
Stanza message("message");
|
||||||
|
message["from"] = from;
|
||||||
|
message["to"] = to;
|
||||||
|
|
||||||
|
XmlNode result("result");
|
||||||
|
result["xmlns"] = MAM_NS;
|
||||||
|
result["queryid"] = queryid;
|
||||||
|
result["id"] = log_line.uuid.value();
|
||||||
|
|
||||||
|
XmlNode forwarded("forwarded");
|
||||||
|
forwarded["xmlns"] = FORWARD_NS;
|
||||||
|
|
||||||
|
XmlNode delay("delay");
|
||||||
|
delay["xmlns"] = DELAY_NS;
|
||||||
|
delay["stamp"] = utils::to_string(log_line.date.value().timeStamp());
|
||||||
|
|
||||||
|
forwarded.add_child(std::move(delay));
|
||||||
|
|
||||||
|
XmlNode submessage("message");
|
||||||
|
submessage["xmlns"] = CLIENT_NS;
|
||||||
|
submessage["from"] = from + "/" + log_line.nick.value();
|
||||||
|
submessage["type"] = "groupchat";
|
||||||
|
|
||||||
|
XmlNode body("body");
|
||||||
|
body.set_inner(log_line.body.value());
|
||||||
|
submessage.add_child(std::move(body));
|
||||||
|
|
||||||
|
forwarded.add_child(std::move(submessage));
|
||||||
|
result.add_child(std::move(forwarded));
|
||||||
|
message.add_child(std::move(result));
|
||||||
|
|
||||||
|
this->send_stanza(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
|
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
|
||||||
{
|
{
|
||||||
auto bare_jid = Jid{user_jid}.bare();
|
auto bare_jid = Jid{user_jid}.bare();
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
namespace db
|
||||||
|
{
|
||||||
|
class MucLogLine;
|
||||||
|
}
|
||||||
struct ListElement;
|
struct ListElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +86,12 @@ public:
|
|||||||
void handle_message(const Stanza& stanza);
|
void handle_message(const Stanza& stanza);
|
||||||
void handle_iq(const Stanza& stanza);
|
void handle_iq(const Stanza& stanza);
|
||||||
|
|
||||||
|
#ifdef USE_DATABASE
|
||||||
|
bool handle_mam_request(const Stanza& stanza);
|
||||||
|
void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
|
||||||
|
const std::string& queryid);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Return the bridge associated with the bare JID. Create a new one
|
* Return the bridge associated with the bare JID. Create a new one
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class XMPPComponent(slixmpp.BaseXMPP):
|
|||||||
self.scenario.steps = []
|
self.scenario.steps = []
|
||||||
self.failed = True
|
self.failed = True
|
||||||
|
|
||||||
def on_end_session(self, event):
|
def on_end_session(self, _):
|
||||||
self.loop.stop()
|
self.loop.stop()
|
||||||
|
|
||||||
def handle_incoming_stanza(self, stanza):
|
def handle_incoming_stanza(self, stanza):
|
||||||
@@ -113,7 +113,11 @@ def match(stanza, xpath):
|
|||||||
'disco_items': 'http://jabber.org/protocol/disco#items',
|
'disco_items': 'http://jabber.org/protocol/disco#items',
|
||||||
'commands': 'http://jabber.org/protocol/commands',
|
'commands': 'http://jabber.org/protocol/commands',
|
||||||
'dataform': 'jabber:x:data',
|
'dataform': 'jabber:x:data',
|
||||||
'version': 'jabber:iq:version'})
|
'version': 'jabber:iq:version',
|
||||||
|
'mam': 'urn:xmpp:mam:1',
|
||||||
|
'delay': 'urn:xmpp:delay',
|
||||||
|
'forward': 'urn:xmpp:forward:0',
|
||||||
|
'client': 'jabber:client'})
|
||||||
return matched
|
return matched
|
||||||
|
|
||||||
|
|
||||||
@@ -1044,7 +1048,47 @@ if __name__ == '__main__':
|
|||||||
("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']",
|
("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']",
|
||||||
"/presence/status[text()='Biboumi note: 1 resources are still in this channel.']")
|
"/presence/status[text()='Biboumi note: 1 resources are still in this channel.']")
|
||||||
),
|
),
|
||||||
])
|
]),
|
||||||
|
Scenario("simple_mam",
|
||||||
|
[
|
||||||
|
handshake_sequence(),
|
||||||
|
# First user joins
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
|
||||||
|
connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']",
|
||||||
|
"/presence/muc_user:x/muc_user:status[@code='110']")
|
||||||
|
),
|
||||||
|
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
|
||||||
|
|
||||||
|
# Send two channel messages
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
|
||||||
|
# Receive the message, forwarded to the two users
|
||||||
|
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"),
|
||||||
|
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"),
|
||||||
|
# Receive the message, forwarded to the two users
|
||||||
|
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"),
|
||||||
|
|
||||||
|
# Retrieve the complete archive
|
||||||
|
partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:1' queryid='qid1' /></iq>"),
|
||||||
|
|
||||||
|
partial(expect_stanza,
|
||||||
|
("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
|
||||||
|
"/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']")
|
||||||
|
),
|
||||||
|
partial(expect_stanza,
|
||||||
|
("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
|
||||||
|
"/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']")
|
||||||
|
),
|
||||||
|
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']")
|
||||||
|
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
failures = 0
|
failures = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user