From 0d2dd71de5292895f69d5f08b000e03e928bdd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 8 Aug 2016 20:49:00 +0200 Subject: =?UTF-8?q?Don=E2=80=99t=20use=20!=20as=20the=20separator=20for=20?= =?UTF-8?q?nicknames,=20use=20%=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s now easier to use. The distinction between a nick and a channel name is based on the first character (by default it's '#' and '&'). The user doesn’t have to worry about which separator to use anymore. fix #3066 --- src/bridge/bridge.cpp | 26 ++++++--- src/bridge/bridge.hpp | 3 +- src/irc/iid.cpp | 109 ++++++++++++++++++------------------ src/irc/iid.hpp | 58 +++++++++++-------- src/irc/irc_client.cpp | 26 ++++----- src/irc/irc_client.hpp | 3 +- src/xmpp/biboumi_adhoc_commands.cpp | 4 +- src/xmpp/biboumi_component.cpp | 61 ++++++++++---------- 8 files changed, 157 insertions(+), 133 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 17d3ec6..ac61dbc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -133,7 +133,7 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) } } -IrcClient* Bridge::find_irc_client(const std::string& hostname) +IrcClient* Bridge::find_irc_client(const std::string& hostname) const { try { @@ -470,7 +470,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) { - Iid iid(nick + "!" + irc_hostname); + Iid iid(nick, irc_hostname, Iid::Type::User); this->send_private_message(iid, "\01PING " + iq_id + "\01"); irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool @@ -541,7 +541,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) { - Iid iid(target + "!" + irc_hostname); + Iid iid(target, irc_hostname, Iid::Type::User); this->send_private_message(iid, "\01VERSION\01"); // TODO, add a timer to remove that waiting iq if the server does not // respond with a matching command before n seconds @@ -590,7 +590,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto it = this->preferred_user_from.find(iid.get_local()); if (it != this->preferred_user_from.end()) { - const auto chan_name = Iid(Jid(it->second).local).get_local(); + const auto chan_name = Iid(Jid(it->second).local, {}).get_local(); for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}]) this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", true); @@ -653,7 +653,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho else body = msg; - const auto encoding = in_encoding_for(*this, {from}); + const auto encoding = in_encoding_for(*this, {from, this}); for (const auto& resource: this->resources_in_server[from]) { this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat"); @@ -696,7 +696,7 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam { std::string encoded_chan_name(chan_name); xep0106::encode(encoded_chan_name); - const auto encoding = in_encoding_for(*this, {encoded_chan_name + '%' + hostname}); + const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel}); this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server( "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); @@ -741,7 +741,7 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string& { const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_iq_version_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); + this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, @@ -753,7 +753,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Forward to the first resource (arbitrary, based on the “order” of the std::set) only const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_ping_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); + this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) @@ -776,7 +776,7 @@ void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_na { for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();) { - Iid iid(Jid(it->second).local); + Iid iid(Jid(it->second).local, {}); if (iid.get_local() == channel_name) it = this->preferred_user_from.erase(it); else @@ -806,6 +806,14 @@ std::unordered_map>& Bridge::get_irc_cli return this->irc_clients; } +std::set Bridge::get_chantypes(const std::string& hostname) const +{ + IrcClient* irc = this->find_irc_client(hostname); + if (!irc) + return {'#', '&'}; + return irc->get_chantypes(); +} + void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource) { auto it = this->resources_in_chan.find(channel); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 69b7bd5..d7b2a5c 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -201,6 +201,7 @@ public: */ void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); + std::set get_chantypes(const std::string& hostname) const; private: /** @@ -217,7 +218,7 @@ private: /** * Idem, but returns nullptr if the server does not exist. */ - IrcClient* find_irc_client(const std::string& hostname); + IrcClient* find_irc_client(const std::string& hostname) const; /** * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 0e2841e..c951a49 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,62 +1,70 @@ #include #include - +#include #include #include -Iid::Iid(const std::string& iid): - is_channel(false), - is_user(false) +Iid::Iid(const std::string local, const std::string server, Iid::Type type): + type(type), + local(local), + server(server) { - const std::string fixed_irc_server = Config::get("fixed_irc_server", ""); - if (fixed_irc_server.empty()) - this->init(iid); - else - this->init_with_fixed_server(iid, fixed_irc_server); } +Iid::Iid(const std::string& iid, const std::set& chantypes) +{ + this->init(iid); + this->set_type(std::set(chantypes)); +} -void Iid::init(const std::string& iid) +Iid::Iid(const std::string& iid, const std::initializer_list& chantypes): + Iid(iid, std::set(chantypes)) { - const std::string::size_type sep = iid.find_first_of("%!"); - if (sep != std::string::npos) - { - if (iid[sep] == '%') - this->is_channel = true; - else - this->is_user = true; - this->set_local(iid.substr(0, sep)); - this->set_server(iid.substr(sep + 1)); - } - else - this->set_server(iid); } -void Iid::init_with_fixed_server(const std::string& iid, const std::string& hostname) +Iid::Iid(const std::string& iid, const Bridge *bridge) +{ + this->init(iid); + const auto chantypes = bridge->get_chantypes(this->server); + this->set_type(chantypes); +} + +void Iid::set_type(const std::set& chantypes) { - this->set_server(hostname); + if (this->local.empty()) + return; - const std::string::size_type sep = iid.find("!"); + if (chantypes.count(this->local[0]) == 1) + this->type = Iid::Type::Channel; + else + this->type = Iid::Type::User; +} - // Without any separator, we consider that it's a channel - if (sep == std::string::npos) +void Iid::init(const std::string& iid) +{ + const std::string fixed_irc_server = Config::get("fixed_irc_server", ""); + + if (fixed_irc_server.empty()) + { + const std::string::size_type sep = iid.find('%'); + if (sep != std::string::npos) { - this->is_channel = true; - this->set_local(iid); + this->set_local(iid.substr(0, sep)); + this->set_server(iid.substr(sep + 1)); + this->type = Iid::Type::Channel; } - else // A separator can be present to differenciate a channel from a user, - // but the part behind it (the hostname) is ignored + else { - this->set_local(iid.substr(0, sep)); - this->is_user = true; + this->set_server(iid); + this->type = Iid::Type::Server; } -} - -Iid::Iid(): - is_channel(false), - is_user(false) -{ + } + else + { + this->set_server(fixed_irc_server); + this->set_local(iid); + } } void Iid::set_local(const std::string& loc) @@ -88,27 +96,18 @@ const std::string& Iid::get_server() const return this->server; } -std::string Iid::get_sep() const -{ - if (this->is_channel) - return "%"; - else if (this->is_user) - return "!"; - return ""; -} - namespace std { const std::string to_string(const Iid& iid) { if (Config::get("fixed_irc_server", "").empty()) - return iid.get_encoded_local() + iid.get_sep() + iid.get_server(); + { + if (iid.type == Iid::Type::Server) + return iid.get_server(); + else + return iid.get_encoded_local() + iid.separator + iid.get_server(); + } else - { - if (iid.get_sep() == "!") - return iid.get_encoded_local() + iid.get_sep(); - else - return iid.get_encoded_local(); - } + return iid.get_encoded_local(); } } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 3b11470..7361c51 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -2,48 +2,64 @@ #include +#include + +class Bridge; /** * A name representing an IRC channel on an IRC server, or an IRC user on an * IRC server, or just an IRC server. * - * The separator for an user is '!', for a channel it's '%'. If no separator - * is present, it's just an irc server. + * The separator is '%' between the local part (nickname or channel) and the + * server part. If no separator is present, it's just an irc server. + * If it is present, the first character of the local part determines if it’s + * a channel or a user: ff the local part is empty or if its first character + * is part of the chantypes characters, then it’s a channel, otherwise it’s + * a user. + * * It’s possible to have an empty-string server, but it makes no sense in - * the biboumi context. + * biboumi’s context. + * + * Assuming the chantypes are '#' and '&': * * #test%irc.example.org has : * - local: "#test" (the # is part of the name, it could very well be absent, or & (for example) instead) * - server: "irc.example.org" - * - is_channel: true - * - is_user: false + * - type: channel * * %irc.example.org: * - local: "" * - server: "irc.example.org" - * - is_channel: true - * - is_user: false - * Note: this is the special empty-string channel, used internal in biboumi + * - type: channel + * Note: this is the special empty-string channel, used internally in biboumi * but has no meaning on IRC. * - * foo!irc.example.org + * foo%irc.example.org * - local: "foo" * - server: "irc.example.org" - * - is_channel: false - * - is_user: true - * Note: the empty-string user (!irc.example.org) has no special meaning in biboumi + * - type: user + * Note: the empty-string user (!irc.example.org) makes no sense for biboumi * * irc.example.org: * - local: "" * - server: "irc.example.org" - * - is_channel: false - * - is_user: false + * - type: server */ class Iid { public: - Iid(const std::string& iid); - Iid(); + enum class Type + { + Channel, + User, + Server, + }; + static constexpr auto separator = "%"; + Iid(const std::string& iid, const std::set& chantypes); + Iid(const std::string& iid, const std::initializer_list& chantypes); + Iid(const std::string& iid, const Bridge* bridge); + Iid(const std::string local, const std::string server, Type type); + Iid() = default; Iid(const Iid&) = default; Iid(Iid&&) = delete; @@ -52,21 +68,19 @@ public: void set_local(const std::string& loc); void set_server(const std::string& serv); + const std::string& get_local() const; const std::string get_encoded_local() const; const std::string& get_server() const; - bool is_channel; - bool is_user; - - std::string get_sep() const; - std::tuple to_tuple() const; + Type type { Type::Server }; + private: void init(const std::string& iid); - void init_with_fixed_server(const std::string& iid, const std::string& hostname); + void set_type(const std::set& chantypes); std::string local; std::string server; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index dd83307..d00b6ee 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -213,7 +213,7 @@ void IrcClient::on_connection_failed(const std::string& reason) // Send an error message for all room that the user wanted to join for (const auto& tuple: this->channels_to_join) { - Iid iid(std::get<0>(tuple) + "%" + this->hostname); + Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes); this->bridge.send_presence_error(iid, this->current_nick, "cancel", "item-not-found", "", reason); @@ -551,7 +551,7 @@ void IrcClient::on_notice(const IrcMessage& message) if (this->nicks_to_treat_as_private.find(nick) != this->nicks_to_treat_as_private.end()) { // We previously sent a message to that nick) - this->bridge.send_message({nick + "!" + this->hostname}, nick, body, + this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body, false); } else @@ -663,12 +663,12 @@ void IrcClient::on_channel_message(const IrcMessage& message) bool muc = true; if (!this->get_channel(iid.get_local())->joined) { - iid.is_user = true; + iid.type = Iid::Type::User; iid.set_local(nick); muc = false; } else - iid.is_channel = true; + iid.type = Iid::Type::Channel; if (!body.empty() && body[0] == '\01') { if (body.substr(1, 6) == "ACTION") @@ -780,7 +780,7 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) Iid iid; iid.set_local(it->first); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; this->bridge.send_nickname_conflict_error(iid, nickname); } } @@ -797,7 +797,7 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) Iid iid; iid.set_local(it->first); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; this->bridge.send_presence_error(iid, nickname, "cancel", "not-acceptable", "", txt); @@ -858,7 +858,7 @@ void IrcClient::on_part(const IrcMessage& message) Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; bool self = channel->get_self()->nick == nick; if (self) { @@ -880,7 +880,7 @@ void IrcClient::on_error(const IrcMessage& message) Iid iid; iid.set_local(it->first); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; IrcChannel* channel = it->second.get(); if (!channel->joined) continue; @@ -908,7 +908,7 @@ void IrcClient::on_quit(const IrcMessage& message) Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false); } } @@ -928,7 +928,7 @@ void IrcClient::on_nick(const IrcMessage& message) Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; const bool self = channel->get_self()->nick == old_nick; const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); @@ -956,7 +956,7 @@ void IrcClient::on_kick(const IrcMessage& message) Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick); } @@ -976,7 +976,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) Iid iid; iid.set_local(message.arguments[0]); iid.set_server(this->hostname); - iid.is_channel = true; + iid.type = Iid::Type::Channel; IrcUser user(message.prefix); std::string mode_arguments; for (size_t i = 1; i < message.arguments.size(); ++i) @@ -1105,7 +1105,7 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message) this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge.send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true); + this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true); } #ifdef BOTAN_FOUND diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index fc3918e..fa35e65 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -280,8 +280,9 @@ public: const Resolver& get_resolver() const { return this->dns_resolver; } - const std::vector& get_sorted_user_modes() const { return sorted_user_modes; } + const std::vector& get_sorted_user_modes() const { return this->sorted_user_modes; } + std::set get_chantypes() const { return this->chantypes; } private: /** * The hostname of the server we are connected to. diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index eec930d..b14081f 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -381,7 +381,7 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); - const Iid iid(target.local); + const Iid iid(target.local, {}); auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain, iid.get_server(), iid.get_local()); @@ -434,7 +434,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); - const Iid iid(target.local); + const Iid iid(target.local, {}); auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain, iid.get_server(), iid.get_local()); for (const XmlNode* field: x->get_children("field", "jabber:x:data")) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a6aac21..f621a6d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -126,7 +126,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) Bridge* bridge = this->get_user_bridge(from_str); Jid to(to_str); Jid from(from_str); - Iid iid(to.local); + Iid iid(to.local, bridge); // An error stanza is sent whenever we exit this function without // disabling this scopeguard. If error_type and error_name are not @@ -142,7 +142,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) }); try { - if (iid.is_channel && !iid.get_server().empty()) + if (iid.type == Iid::Type::Channel && !iid.get_server().empty()) { // presence toward a MUC that corresponds to an irc channel, or a // dummy channel if iid.chan is empty if (type.empty()) @@ -191,7 +191,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) type = "normal"; Bridge* bridge = this->get_user_bridge(from); Jid to(to_str); - Iid iid(to.local); + Iid iid(to.local, bridge); std::string error_type("cancel"); std::string error_name("internal-server-error"); @@ -202,7 +202,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) const XmlNode* body = stanza.get_child("body", COMPONENT_NS); try { // catch IRCNotConnected exceptions - if (type == "groupchat" && iid.is_channel) + if (type == "groupchat" && iid.type == Iid::Type::Channel) { if (body && !body->get_inner().empty()) { @@ -234,27 +234,27 @@ void BiboumiComponent::handle_message(const Stanza& stanza) if (body && !body->get_inner().empty()) { // a message for nick!server - if (iid.is_user && !iid.get_local().empty()) + if (iid.type == Iid::Type::User && !iid.get_local().empty()) { bridge->send_private_message(iid, body->get_inner()); bridge->remove_preferred_from_jid(iid.get_local()); } - else if (!iid.is_user && !to.resource.empty()) + else if (iid.type != Iid::Type::User && !to.resource.empty()) { // a message for chan%server@biboumi/Nick or // server@biboumi/Nick // Convert that into a message to nick!server - Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); + Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User); bridge->send_private_message(user_iid, body->get_inner()); bridge->set_preferred_from_jid(user_iid.get_local(), to_str); } - else if (!iid.is_user && !iid.is_channel) + else if (iid.type == Iid::Type::Server) { // Message sent to the server JID // Convert the message body into a raw IRC message bridge->send_raw_message(iid.get_server(), body->get_inner()); } } } - else if (iid.is_user) + else if (iid.type == Iid::Type::User) this->send_invalid_user_error(to.local, from); } catch (const IRCNotConnected& ex) { @@ -321,7 +321,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) std::string affiliation = child->get_tag("affiliation"); if (!nick.empty()) { - Iid iid(to.local); + Iid iid(to.local, {}); if (role == "none") { // This is a kick std::string reason; @@ -345,15 +345,17 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) // Depending on the 'to' jid in the request, we use one adhoc // command handler or an other - Iid iid(to.local); + Iid iid(to.local, {}); AdhocCommandsHandler* adhoc_handler; - if (!to.local.empty() && !iid.is_user && !iid.is_channel) - adhoc_handler = &this->irc_server_adhoc_commands_handler; - else if (!to.local.empty() && iid.is_channel) - adhoc_handler = &this->irc_channel_adhoc_commands_handler; - else + if (to.local.empty()) adhoc_handler = &this->adhoc_commands_handler; - + else + { + if (iid.type == Iid::Type::Server) + adhoc_handler = &this->irc_server_adhoc_commands_handler; + else + adhoc_handler = &this->irc_channel_adhoc_commands_handler; + } // Execute the command, if any, and get a result XmlNode that we // insert in our response XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query); @@ -384,13 +386,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if ((query = stanza.get_child("query", VERSION_NS))) { - Iid iid(to.local); - if (iid.is_user || - (iid.is_channel && !to.resource.empty())) + Iid iid(to.local, bridge); + if (iid.type != Iid::Type::Server && !to.resource.empty()) { // Get the IRC user version std::string target; - if (iid.is_user) + if (iid.type == Iid::Type::User) target = iid.get_local(); else target = to.resource; @@ -406,7 +407,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) { - Iid iid(to.local); + Iid iid(to.local, bridge); const std::string node = query->get_tag("node"); if (node == ADHOC_NS) { @@ -419,7 +420,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->adhoc_commands_handler); stanza_error.disable(); } - else if (!iid.is_user && !iid.is_channel) + else if (iid.type == Iid::Type::Server) { // Get the server's adhoc commands this->send_adhoc_commands_list(id, from, to_str, (Config::get("admin", "") == @@ -427,7 +428,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->irc_server_adhoc_commands_handler); stanza_error.disable(); } - else if (!iid.is_user && iid.is_channel) + else if (iid.type == Iid::Type::Channel) { // Get the channel's adhoc commands this->send_adhoc_commands_list(id, from, to_str, (Config::get("admin", "") == @@ -436,7 +437,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } - else if (node.empty() && !iid.is_user && !iid.is_channel) + else if (node.empty() && iid.type == Iid::Type::Server) { // Disco on an IRC server: get the list of channels bridge->send_irc_channel_list_request(iid, id, from); stanza_error.disable(); @@ -444,13 +445,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if ((query = stanza.get_child("ping", PING_NS))) { - Iid iid(to.local); - if (iid.is_user) + Iid iid(to.local, bridge); + if (iid.type == Iid::Type::User) { // Ping any user (no check on the nick done ourself) bridge->send_irc_user_ping_request(iid.get_server(), iid.get_local(), id, from, to_str); } - else if (iid.is_channel && !to.resource.empty()) + else if (iid.type == Iid::Type::Channel && !to.resource.empty()) { // Ping a room participant (we check if the nick is in the room) bridge->send_irc_participant_ping_request(iid, to.resource, id, from, to_str); @@ -481,7 +482,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) version = version_node->get_inner(); if (os_node) os = os_node->get_inner(); - const Iid iid(to.local); + const Iid iid(to.local, bridge); bridge->send_xmpp_version_to_irc(iid, name, version, os); } else @@ -604,7 +605,7 @@ void BiboumiComponent::send_ping_request(const std::string& from, "the response mismatches the 'from' of the request"); } else - bridge->send_irc_ping_result(from, id); + bridge->send_irc_ping_result({from, bridge}, id); }; this->waiting_iq[id] = result_cb; } -- cgit v1.2.3 From 8ec823be4fc587abb7282a06a12f9df9c37810d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 12 Aug 2016 16:39:19 +0200 Subject: Save received and sent messages into the database --- src/bridge/bridge.cpp | 10 ++++++++++ src/database/database.cpp | 31 +++++++++++++++++++++++++++++++ src/database/database.hpp | 8 ++++++++ 3 files changed, 49 insertions(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ac61dbc..1812611 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -224,6 +224,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else irc->send_channel_message(iid.get_local(), line); + + const auto xmpp_body = this->make_xmpp_body(line); + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), irc->get_own_nick()); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid + "/" + resource); @@ -578,10 +583,15 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto encoding = in_encoding_for(*this, iid); if (muc) { + const auto xmpp_body = this->make_xmpp_body(body, encoding); + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), nick); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + } } else diff --git a/src/database/database.cpp b/src/database/database.cpp index 61e1b47..5513946 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include using namespace std::string_literals; @@ -79,9 +81,38 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons return coptions; } +void Database::store_muc_message(const std::string& owner, const Iid& iid, + Database::time_point date, + const std::string& body, + const std::string& nick) +{ + db::MucLogLine line(*Database::db); + + line.uuid = Database::gen_uuid(); + line.owner = owner; + line.ircChanName = iid.get_local(); + line.ircServerName = iid.get_server(); + line.date = date.time_since_epoch().count(); + line.body = body; + line.nick = nick; + + line.update(); +} + void Database::close() { Database::db.reset(nullptr); } + +std::string Database::gen_uuid() +{ + char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; +} + + #endif diff --git a/src/database/database.hpp b/src/database/database.hpp index 7173bcd..b11332e 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -9,10 +9,14 @@ #include #include +#include + +class Iid; class Database { public: + using time_point = std::chrono::system_clock::time_point; Database() = default; ~Database() = default; @@ -41,11 +45,15 @@ public: const std::string& server, const std::string& channel); + static void store_muc_message(const std::string& owner, const Iid& iid, + time_point date, const std::string& body, const std::string& nick); + static void close(); static void open(const std::string& filename, const std::string& db_type="sqlite3"); private: + static std::string gen_uuid(); static std::unique_ptr db; }; #endif /* USE_DATABASE */ -- cgit v1.2.3 From 787563722c36bc63223655d337d98caac82dc44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 13 Aug 2016 01:46:11 +0200 Subject: Only save the messages into the db if we are actually using a db --- src/bridge/bridge.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1812611..23fc71d 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -225,10 +225,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) else irc->send_channel_message(iid.get_local(), line); +#ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), irc->get_own_nick()); - +#endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid + "/" + resource); @@ -583,10 +584,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto encoding = in_encoding_for(*this, iid); if (muc) { +#ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), nick); - +#endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { this->xmpp.send_muc_message(std::to_string(iid), nick, -- cgit v1.2.3 From 6bbab9d943a56e3cd20a2ac96cce1e9056d148cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 16 Aug 2016 10:44:13 +0200 Subject: Add ping to the disco info of the gateay --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f621a6d..a0222f5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -558,7 +558,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) + for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS}) { XmlNode feature("feature"); feature["var"] = ns; -- cgit v1.2.3 From 663d4ad54a014b2ced62610098a6f5676f813d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 16 Aug 2016 15:59:00 +0200 Subject: Forward mediated invitations (XMPP to IRC only) --- src/bridge/bridge.cpp | 16 ++++++++++++++++ src/bridge/bridge.hpp | 2 ++ src/irc/irc_client.cpp | 15 +++++++++++++-- src/irc/irc_client.hpp | 5 +++++ src/xmpp/biboumi_component.cpp | 13 +++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 23fc71d..8323c77 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -543,6 +543,22 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& "", true); } +void Bridge::send_irc_invitation(const Iid& iid, const std::string to) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + Jid to_jid(to); + std::string target_nick; + // Many ways to address a nick: + // A jid (ANY jid…) with a resource + if (!to_jid.resource.empty()) + target_nick = to_jid.resource; + else if (!to_jid.local.empty()) // A jid with a iid with a local part + target_nick = Iid(to_jid.local, {}).get_local(); + else + target_nick = to; // Not a jid, just the nick + irc->send_invitation(iid.get_local(), target_nick); +} + void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index d7b2a5c..a1ff3ad 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -105,6 +105,8 @@ public: void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + void send_irc_invitation(const Iid& iid, const std::string to); + /*** ** ** From IRC to XMPP. diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d00b6ee..6d4df4a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -64,6 +64,8 @@ static const std::unordered_mapsend_message(IrcMessage("LIST", {})); } +void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick) +{ + this->send_message(IrcMessage("INVITE", {nick, chan_name})); +} + void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic) { this->send_message(IrcMessage("TOPIC", {chan_name, topic})); @@ -803,7 +809,6 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) "", txt); } } - void IrcClient::on_generic_error(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? @@ -811,6 +816,12 @@ void IrcClient::on_generic_error(const IrcMessage& message) this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix); } +void IrcClient::on_useronchannel(const IrcMessage& message) +{ + this->send_gateway_message(message.arguments[1] + " " + message.arguments[3] + " " + + message.arguments[2]); +} + void IrcClient::on_welcome_message(const IrcMessage& message) { this->current_nick = message.arguments[0]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index fa35e65..6fa40ce 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -129,6 +129,7 @@ public: * Send the LIST irc command */ void send_list_command(); + void send_invitation(const std::string& chan_name, const std::string& nick); void send_topic_command(const std::string& chan_name, const std::string& topic); /** * Send the QUIT irc command @@ -234,6 +235,10 @@ public: * Idem, but for when the user changes their nickname too quickly */ void on_nickname_change_too_fast(const IrcMessage& message); + /** + * An error when we try to invite a user already in the channel + */ + void on_useronchannel(const IrcMessage& message); /** * Handles most errors from the server by just forwarding the message to the user. */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a0222f5..e7549df 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -254,6 +254,19 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } } } + else if (type == "normal" && iid.type == Iid::Type::Channel) + { + if (const XmlNode* x = stanza.get_child("x", MUC_USER_NS)) + if (const XmlNode* invite = x->get_child("invite", MUC_USER_NS)) + { + const auto invite_to = invite->get_tag("to"); + if (!invite_to.empty()) + { + bridge->send_irc_invitation(iid, invite_to); + } + } + + } else if (iid.type == Iid::Type::User) this->send_invalid_user_error(to.local, from); } catch (const IRCNotConnected& ex) -- cgit v1.2.3 From 5406de35a39c935a19460da06bf3dcd3948a00d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Aug 2016 20:44:00 +0200 Subject: On a client error, do not QUIT, just make the resource leave all channels This should fix #3205 --- src/bridge/bridge.cpp | 16 +++++++++++++++- src/bridge/bridge.hpp | 6 +++++- src/xmpp/biboumi_component.cpp | 17 +++++++++-------- 3 files changed, 29 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8323c77..f6fefd9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -60,6 +60,20 @@ void Bridge::shutdown(const std::string& exit_message) } } +void Bridge::remove_resource(const std::string& resource, + const std::string& part_message) +{ + const auto resources_in_chan_copy = this->resources_in_chan; + for (const auto& chan_pair: resources_in_chan_copy) + { + const ChannelKey& channel_key = chan_pair.first; + const std::set& resources = chan_pair.second; + if (resources.count(resource)) + this->leave_irc_channel({std::get<0>(channel_key), std::get<1>(channel_key), {}}, + part_message, resource); + } +} + void Bridge::clean() { auto it = this->irc_clients.begin(); @@ -330,7 +344,7 @@ void Bridge::send_raw_message(const std::string& hostname, const std::string& bo irc->send_raw(body); } -void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource) +void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource) { IrcClient* irc = this->get_irc_client(iid.get_server()); const auto key = iid.to_tuple(); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index a1ff3ad..bcad030 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -44,6 +44,10 @@ public: * QUIT all connected IRC servers. */ void shutdown(const std::string& exit_message); + /** + * PART the given resource from all the channels + */ + void remove_resource(const std::string& resource, const std::string& part_message); /** * Remove all inactive IrcClients */ @@ -70,7 +74,7 @@ public: void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void send_raw_message(const std::string& hostname, const std::string& body); - void leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource); + void leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e7549df..cb9aac8 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -180,23 +180,24 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) void BiboumiComponent::handle_message(const Stanza& stanza) { - std::string from = stanza.get_tag("from"); + std::string from_str = stanza.get_tag("from"); std::string id = stanza.get_tag("id"); std::string to_str = stanza.get_tag("to"); std::string type = stanza.get_tag("type"); - if (from.empty()) + if (from_str.empty()) return; if (type.empty()) type = "normal"; - Bridge* bridge = this->get_user_bridge(from); + Bridge* bridge = this->get_user_bridge(from_str); + Jid from(from_str); Jid to(to_str); Iid iid(to.local, bridge); std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("message", from, to_str, id, + this->send_stanza_error("message", from_str, to_str, id, error_type, error_name, ""); }); const XmlNode* body = stanza.get_child("body", COMPONENT_NS); @@ -216,7 +217,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) { const XmlNode* error = stanza.get_child("error", COMPONENT_NS); // Only a set of errors are considered “fatal”. If we encounter one of - // them, we purge (we disconnect the user from all the IRC servers). + // them, we purge (we disconnect that resource from all the IRC servers) // We consider this to be true, unless the error condition is // specified and is not in the kickable_errors set bool kickable_error = true; @@ -227,7 +228,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) kickable_error = false; } if (kickable_error) - bridge->shutdown("Error from remote client"); + bridge->remove_resource(from.resource, "Error from remote client"); } else if (type == "chat") { @@ -268,10 +269,10 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } else if (iid.type == Iid::Type::User) - this->send_invalid_user_error(to.local, from); + this->send_invalid_user_error(to.local, from_str); } catch (const IRCNotConnected& ex) { - this->send_stanza_error("message", from, to_str, id, + this->send_stanza_error("message", from_str, to_str, id, "cancel", "remote-server-not-found", "Not connected to IRC server "s + ex.hostname, true); -- cgit v1.2.3 From 4c8fb9a0e314db88dec1f105144aadafc5796ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Aug 2016 03:42:40 +0200 Subject: Forward IRC invites to XMPP --- src/bridge/bridge.cpp | 6 ++++++ src/bridge/bridge.hpp | 2 ++ src/irc/irc_client.cpp | 12 ++++++++++++ src/irc/irc_client.hpp | 1 + src/xmpp/biboumi_component.cpp | 16 ++++++++++++++++ src/xmpp/biboumi_component.hpp | 1 + 6 files changed, 38 insertions(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f6fefd9..f69da77 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -798,6 +798,12 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } +void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author) +{ + for (const auto& resource: this->resources_in_server[iid.get_server()]) + this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author); +} + void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) { auto it = this->preferred_user_from.find(nick); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index bcad030..6fdbcc9 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -175,6 +175,8 @@ public: */ void send_xmpp_ping_request(const std::string& nick, const std::string& hostname, const std::string& id); + void send_xmpp_invitation(const Iid& iid, const std::string& author); + /** * Misc */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6d4df4a..e4b6377 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -75,6 +75,7 @@ static const std::unordered_mapbridge.kick_muc_user(std::move(iid), target, reason, author.nick); } +void IrcClient::on_invite(const IrcMessage& message) +{ + const std::string& author = message.arguments[0]; + Iid iid; + iid.set_local(message.arguments[1]); + iid.set_server(this->hostname); + iid.type = Iid::Type::Channel; + + this->bridge.send_xmpp_invitation(iid, author); +} + void IrcClient::on_mode(const IrcMessage& message) { const std::string target = message.arguments[0]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 6fa40ce..9f53c3c 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -249,6 +249,7 @@ public: void on_welcome_message(const IrcMessage& message); void on_part(const IrcMessage& message); void on_error(const IrcMessage& message); + void on_invite(const IrcMessage& message); void on_nick(const IrcMessage& message); void on_kick(const IrcMessage& message); void on_mode(const IrcMessage& message); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index cb9aac8..5c52494 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -645,3 +645,19 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, iq.add_child(std::move(query)); this->send_stanza(iq); } + +void BiboumiComponent::send_invitation(const std::string& room_target, + const std::string& jid_to, + const std::string& author_nick) +{ + Stanza message("message"); + message["from"] = room_target + "@" + this->served_hostname; + message["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode invite("invite"); + invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + x.add_child(std::move(invite)); + message.add_child(std::move(x)); + this->send_stanza(message); +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 24d768a..0dbf8f1 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -74,6 +74,7 @@ public: void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from, const std::vector& rooms_list); + void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); /** * Handle the various stanza types */ -- cgit v1.2.3 From 7d05f9b6aedd9153bd7787a5a83b9840b1991a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 Aug 2016 21:13:42 +0200 Subject: Fix the author of the IRC invite --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e4b6377..4cd437d 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -974,13 +974,13 @@ void IrcClient::on_kick(const IrcMessage& message) void IrcClient::on_invite(const IrcMessage& message) { - const std::string& author = message.arguments[0]; + IrcUser author(message.prefix); Iid iid; iid.set_local(message.arguments[1]); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.send_xmpp_invitation(iid, author); + this->bridge.send_xmpp_invitation(iid, author.nick); } void IrcClient::on_mode(const IrcMessage& message) -- cgit v1.2.3 From 992fa938951558f4515145c9b82af0123c979a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 21 Aug 2016 01:04:32 +0200 Subject: Add get_first_non_empty and use it into Database to simplify a little bit --- src/database/database.cpp | 11 ++++++----- src/utils/get_first_non_empty.cpp | 11 +++++++++++ src/utils/get_first_non_empty.hpp | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/utils/get_first_non_empty.cpp create mode 100644 src/utils/get_first_non_empty.hpp (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index 5513946..3891f41 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include using namespace std::string_literals; @@ -73,10 +73,11 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons { auto coptions = Database::get_irc_channel_options(owner, server, channel); auto soptions = Database::get_irc_server_options(owner, server); - if (coptions.encodingIn.value().empty()) - coptions.encodingIn = soptions.encodingIn; - if (coptions.encodingOut.value().empty()) - coptions.encodingOut = soptions.encodingOut; + + coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), + soptions.encodingIn.value()); + coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), + soptions.encodingOut.value()); return coptions; } diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/src/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/src/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template +T get_first_non_empty(T&& last) +{ + return last; +} + +template +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward(args)...); +} -- cgit v1.2.3 From 6776b827d243ec0e018eac8233c5df402030640e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:38:44 +0200 Subject: Add a global configure ad-hoc command, with max history length --- src/database/database.cpp | 36 ++++++++++++++++++ src/database/database.hpp | 5 ++- src/xmpp/biboumi_adhoc_commands.cpp | 74 ++++++++++++++++++++++++++++++++++--- src/xmpp/biboumi_adhoc_commands.hpp | 5 ++- src/xmpp/biboumi_component.cpp | 8 ++-- 5 files changed, 116 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index 3891f41..acf57d1 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -31,6 +31,19 @@ void Database::set_verbose(const bool val) Database::db->verbose = val; } +db::GlobalOptions Database::get_global_options(const std::string& owner) +{ + try { + auto options = litesql::select(*Database::db, + db::GlobalOptions::Owner == owner).one(); + return options; + } catch (const litesql::NotFound& e) { + db::GlobalOptions options(*Database::db); + options.owner = owner; + return options; + } +} + db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server) { @@ -79,6 +92,29 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), soptions.encodingOut.value()); + coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), + soptions.maxHistoryLength.value()); + + return coptions; +} + +db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, + const std::string& server, + const std::string& channel) +{ + auto coptions = Database::get_irc_channel_options(owner, server, channel); + auto soptions = Database::get_irc_server_options(owner, server); + auto goptions = Database::get_global_options(owner); + + coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), + soptions.encodingIn.value()); + coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), + soptions.encodingOut.value()); + + coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), + soptions.maxHistoryLength.value(), + goptions.maxHistoryLength.value()); + return coptions; } diff --git a/src/database/database.hpp b/src/database/database.hpp index b11332e..d1be2fd 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -36,6 +36,7 @@ public: * Return the object from the db. Create it beforehand (with all default * values) if it is not already present. */ + static db::GlobalOptions get_global_options(const std::string& owner); static db::IrcServerOptions get_irc_server_options(const std::string& owner, const std::string& server); static db::IrcChannelOptions get_irc_channel_options(const std::string& owner, @@ -44,7 +45,9 @@ public: static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server, const std::string& channel); - + static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner, + const std::string& server, + const std::string& channel); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index b14081f..2050edf 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -11,10 +11,6 @@ #include #endif -#include - -#include - using namespace std::string_literals; void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) @@ -114,6 +110,72 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X } #ifdef USE_DATABASE + +void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + auto options = Database::get_global_options(owner.bare()); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure some global default settings."); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Edit the form, to configure your global settings for the component."); + x.add_child(std::move(instructions)); + + XmlNode required("required"); + + XmlNode max_histo_length("field"); + max_histo_length["var"] = "max_history_length"; + max_histo_length["type"] = "text-single"; + max_histo_length["label"] = "Max history length"; + max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; + + XmlNode max_histo_length_value("value"); + max_histo_length_value.set_inner(std::to_string(options.maxHistoryLength.value())); + max_histo_length.add_child(std::move(max_histo_length_value)); + x.add_child(std::move(max_histo_length)); + + command_node.add_child(std::move(x)); +} + +void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + const Jid owner(session.get_owner_jid()); + auto options = Database::get_global_options(owner.bare()); + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + const XmlNode* value = field->get_child("value", "jabber:x:data"); + + if (field->get_tag("var") == "max_history_length" && + value && !value->get_inner().empty()) + options.maxHistoryLength = value->get_inner(); + } + + options.update(); + + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration successfully applied."); + command_node.add_child(std::move(note)); + return; + } + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + error.add_child(std::move(condition)); + command_node.add_child(std::move(error)); + session.terminate(); +} + void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) { const Jid owner(session.get_owner_jid()); @@ -315,7 +377,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com } else if (field->get_tag("var") == "verify_cert" && value - && !value->get_inner().empty()) + && !value->get_inner().empty()) { auto val = to_bool(value->get_inner()); options.verifyCert = val; @@ -442,7 +504,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co const XmlNode* value = field->get_child("value", "jabber:x:data"); if (field->get_tag("var") == "encoding_out" && - value && !value->get_inner().empty()) + value && !value->get_inner().empty()) options.encodingOut = value->get_inner(); else if (field->get_tag("var") == "encoding_in" && diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 2763a9f..7be5509 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -10,6 +10,9 @@ class XmppComponent; void DisconnectUserStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); + void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); @@ -19,5 +22,3 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); - - diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 5c52494..9acccdb 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -63,11 +63,13 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); + AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); if (!Config::get("fixed_irc_server", "").empty()) - { this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_server_command)); - } + configure_server_command)); + else + this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", + configure_global_command)); #endif this->irc_server_adhoc_commands_handler.get_commands() = { -- cgit v1.2.3 From 593b3268273cac2fa58257ee8e51ce1a6de30872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:39:11 +0200 Subject: When saving the logs, the date must be in seconds, not nanoseconds --- src/database/database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index acf57d1..fce0f45 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -129,7 +129,7 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.owner = owner; line.ircChanName = iid.get_local(); line.ircServerName = iid.get_server(); - line.date = date.time_since_epoch().count(); + line.date = date.time_since_epoch().count() / 1'000'000'000; line.body = body; line.nick = nick; -- cgit v1.2.3 From d1626c929f1d313c2f0f85b7d8b756a8d488d1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:44:17 +0200 Subject: When joining a channel, send the most recent history found in the database --- src/bridge/bridge.cpp | 20 ++++++++++++++++++++ src/bridge/bridge.hpp | 5 +++++ src/database/database.cpp | 9 +++++++++ src/database/database.hpp | 2 ++ src/irc/irc_client.cpp | 4 +++- 5 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f69da77..11393d4 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -744,6 +744,26 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam } +void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name) +{ + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + this->send_room_history(hostname, chan_name, resource); +} + +void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource) +{ +#ifdef USE_DATABASE + const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name); + const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value()); + for (const auto& line: lines) + { + const auto seconds = line.date.value().timeStamp(); + this->xmpp.send_history_message(chan_name + "%" + hostname, line.nick.value(), line.body.value(), + this->user_jid + "/" + resource, seconds); + } +#endif +} + std::string Bridge::get_own_nick(const Iid& iid) { IrcClient* irc = this->find_irc_client(iid.get_server()); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 6fdbcc9..c26995f 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -137,6 +137,11 @@ public: */ void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who); void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who, const std::string& resource); + /** + * Send the MUC history to the user + */ + void send_room_history(const std::string& hostname, const std::string& chan_name); + void send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource); /** * Send a MUC message from some participant */ diff --git a/src/database/database.cpp b/src/database/database.cpp index fce0f45..48cdea8 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -136,6 +136,15 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, const int limit) +{ + auto res = litesql::select(*Database::db, + db::MucLogLine::Owner == owner && + db::MucLogLine::IrcChanName == chan_name && + db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Date, false).limit(limit).all(); + return {res.rbegin(), res.rend()}; +} + void Database::close() { Database::db.reset(nullptr); diff --git a/src/database/database.hpp b/src/database/database.hpp index d1be2fd..14012ff 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -48,6 +48,8 @@ public: static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel); + static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, + const int limit); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4cd437d..59da97e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -755,7 +755,9 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->joined = true; - this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); + this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), + channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); + this->bridge.send_room_history(this->hostname, chan_name); this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } -- cgit v1.2.3 From 38a4bcb2d2fd9bb535801a0209d614206204a460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:47:55 +0200 Subject: Only save messages coming from real MUC participants in the database Not messages coming from the room, for example --- src/bridge/bridge.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 11393d4..ad1a45e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -616,8 +616,9 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), - std::get<0>(xmpp_body), nick); + if (!nick.empty()) + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), nick); #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { -- cgit v1.2.3 From e13d3fdf4d4754c85e7e05e98592afb71d22be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 21:29:22 +0200 Subject: Move get_first_non_empty to louloulibs/utils --- src/utils/get_first_non_empty.cpp | 11 ----------- src/utils/get_first_non_empty.hpp | 20 -------------------- 2 files changed, 31 deletions(-) delete mode 100644 src/utils/get_first_non_empty.cpp delete mode 100644 src/utils/get_first_non_empty.hpp (limited to 'src') diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp deleted file mode 100644 index 5b3bedb..0000000 --- a/src/utils/get_first_non_empty.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -bool is_empty(const std::string& val) -{ - return val.empty(); -} - -bool is_empty(const int& val) -{ - return val == 0; -} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp deleted file mode 100644 index a38f5fb..0000000 --- a/src/utils/get_first_non_empty.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -bool is_empty(const std::string& val); -bool is_empty(const int& val); - -template -T get_first_non_empty(T&& last) -{ - return last; -} - -template -T get_first_non_empty(T&& first, Args&&... args) -{ - if (!is_empty(first)) - return first; - return get_first_non_empty(std::forward(args)...); -} -- cgit v1.2.3 From cfebca4d7c46959fc490cb9c72363d4ac3ee0c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 00:11:51 +0200 Subject: Only save the logs if recordHistory global config option is true --- src/bridge/bridge.cpp | 19 +++++++++++++++---- src/bridge/bridge.hpp | 8 ++++++++ src/xmpp/biboumi_adhoc_commands.cpp | 32 ++++++++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ad1a45e..9b91474 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -32,6 +32,10 @@ Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ xmpp(xmpp), poller(poller) { +#ifdef USE_DATABASE + const auto options = Database::get_global_options(this->user_jid); + this->set_record_history(options.recordHistory.value()); +#endif } /** @@ -241,8 +245,9 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), - std::get<0>(xmpp_body), irc->get_own_nick()); + if (this->record_history) + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), irc->get_own_nick()); #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), @@ -616,7 +621,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); - if (!nick.empty()) + if (!nick.empty() && this->record_history) Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), nick); #endif @@ -971,7 +976,6 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin { if (user->nick != self->nick) { - log_debug(user->nick); this->send_user_join(iid.get_server(), iid.get_encoded_local(), user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()), false, resource); @@ -982,3 +986,10 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin true, resource); this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } + +#ifdef USE_DATABASE +void Bridge::set_record_history(const bool val) +{ + this->record_history = val; +} +#endif \ No newline at end of file diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c26995f..0e1fbf4 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -13,6 +13,8 @@ #include #include +#include + class BiboumiComponent; class Poller; @@ -215,6 +217,9 @@ public: void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); std::set get_chantypes(const std::string& hostname) const; +#ifdef USE_DATABASE + void set_record_history(const bool val); +#endif private: /** @@ -295,6 +300,9 @@ private: * TODO: send message history */ void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); +#ifdef USE_DATABASE + bool record_history { true }; +#endif }; struct IRCNotConnected: public std::exception diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 2050edf..64c16a9 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -135,16 +135,32 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman max_histo_length["label"] = "Max history length"; max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; - XmlNode max_histo_length_value("value"); - max_histo_length_value.set_inner(std::to_string(options.maxHistoryLength.value())); - max_histo_length.add_child(std::move(max_histo_length_value)); + XmlNode value("value"); + value.set_inner(std::to_string(options.maxHistoryLength.value())); + max_histo_length.add_child(std::move(value)); x.add_child(std::move(max_histo_length)); + XmlNode record_history("field"); + record_history["var"] = "record_history"; + record_history["type"] = "boolean"; + record_history["label"] = "Record history"; + record_history["desc"] = "Whether to save the messages into the database, or not"; + + value.set_name("value"); + if (options.recordHistory.value()) + value.set_inner("true"); + else + value.set_inner("false"); + record_history.add_child(std::move(value)); + x.add_child(std::move(record_history)); + command_node.add_child(std::move(x)); } -void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { + BiboumiComponent& biboumi_component = static_cast(xmpp_component); + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) { @@ -157,6 +173,14 @@ void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& comman if (field->get_tag("var") == "max_history_length" && value && !value->get_inner().empty()) options.maxHistoryLength = value->get_inner(); + else if (field->get_tag("var") == "record_history" && + value && !value->get_inner().empty()) + { + options.recordHistory = to_bool(value->get_inner()); + Bridge* bridge = biboumi_component.find_user_bridge(owner.bare()); + if (bridge) + bridge->set_record_history(options.recordHistory.value()); + } } options.update(); -- cgit v1.2.3 From 88499d037962b4a0bd85d812cb77ef0452c985f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 00:28:12 +0200 Subject: If the maxHistoryLength is negative, make it 0 Instead of doing a broken SQL request that returns weird lines --- src/database/database.cpp | 4 +++- src/database/database.hpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index 48cdea8..009ff0e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -136,8 +136,10 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } -std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, const int limit) +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) { + if (limit < 0) + limit = 0; auto res = litesql::select(*Database::db, db::MucLogLine::Owner == owner && db::MucLogLine::IrcChanName == chan_name && diff --git a/src/database/database.hpp b/src/database/database.hpp index 14012ff..e7fdd5f 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -49,7 +49,7 @@ public: const std::string& server, const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, - const int limit); + int limit); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); -- cgit v1.2.3 From 1ffc3677536fd2978c5f4a77fc6148e80ff1256b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 02:30:40 +0200 Subject: Send the muc history when a second (or more) resource joins a room --- src/bridge/bridge.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9b91474..e24383e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -984,6 +984,7 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin this->send_user_join(iid.get_server(), iid.get_encoded_local(), self, self->get_most_significant_mode(irc->get_sorted_user_modes()), true, resource); + this->send_room_history(iid.get_server(), iid.get_local(), resource); this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } -- cgit v1.2.3 From 00e88f6b2a8aa97d94ccb3ea24dec3f407b08597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 20:28:37 +0200 Subject: Add missing algorithm include --- src/xmpp/biboumi_adhoc_commands.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 64c16a9..af7e473 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include -- cgit v1.2.3 From 7536a1b3f38fbf093c1629b0db209754ada0c906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 25 Aug 2016 19:43:51 +0200 Subject: 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. --- src/database/database.cpp | 10 +++--- src/xmpp/biboumi_component.cpp | 79 +++++++++++++++++++++++++++++++++++++++++- src/xmpp/biboumi_component.hpp | 10 ++++++ 3 files changed, 93 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index 009ff0e..be0da8e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -138,13 +138,13 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) { - if (limit < 0) - limit = 0; - auto res = litesql::select(*Database::db, + if (limit == -1) + limit = 1024; + const auto& res = litesql::select(*Database::db, db::MucLogLine::Owner == owner && db::MucLogLine::IrcChanName == chan_name && - db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Date, false).limit(limit).all(); - return {res.rbegin(), res.rend()}; + db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Id, false).limit(limit).all(); + return {res.crbegin(), res.crend()}; } void Database::close() diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 9acccdb..a49f52e 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -8,8 +8,9 @@ #include #include #include -#include #include +#include +#include #include #include @@ -25,6 +26,8 @@ # include #endif +#include + using namespace std::string_literals; static std::set kickable_errors{ @@ -383,6 +386,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->send_stanza(response); 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") { @@ -525,6 +535,73 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) 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) { auto bare_jid = Jid{user_jid}.bare(); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 0dbf8f1..1844451 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -9,6 +9,10 @@ #include #include +namespace db +{ +class MucLogLine; +} struct ListElement; /** @@ -82,6 +86,12 @@ public: void handle_message(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: /** * Return the bridge associated with the bare JID. Create a new one -- cgit v1.2.3 From 3047bd41b212390da8e3a4dbcf351e79879042dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Sep 2016 21:04:21 +0200 Subject: MAM results can be filtered by start and end dates --- src/database/database.cpp | 32 ++++++++++++++++++++++++-------- src/database/database.hpp | 2 +- src/xmpp/biboumi_component.cpp | 37 ++++++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index be0da8e..e995d95 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace std::string_literals; @@ -136,14 +137,30 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } -std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, + int limit, const std::string& start, const std::string& end) { - if (limit == -1) - limit = 1024; - const auto& res = litesql::select(*Database::db, - db::MucLogLine::Owner == owner && - db::MucLogLine::IrcChanName == chan_name && - db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Id, false).limit(limit).all(); + auto request = litesql::select(*Database::db, + db::MucLogLine::Owner == owner && + db::MucLogLine::IrcChanName == chan_name && + db::MucLogLine::IrcServerName == server); + request.orderBy(db::MucLogLine::Id, false); + + if (limit >= 0) + request.limit(limit); + if (!start.empty()) + { + const auto start_time = utils::parse_datetime(start); + if (start_time != -1) + request.where(db::MucLogLine::Date >= start_time); + } + if (!end.empty()) + { + const auto end_time = utils::parse_datetime(end); + if (end_time != -1) + request.where(db::MucLogLine::Date <= end_time); + } + const auto& res = request.all(); return {res.crbegin(), res.crend()}; } @@ -152,7 +169,6 @@ void Database::close() Database::db.reset(nullptr); } - std::string Database::gen_uuid() { char uuid_str[37]; diff --git a/src/database/database.hpp b/src/database/database.hpp index e7fdd5f..6823574 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -49,7 +49,7 @@ public: const std::string& server, const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, - int limit); + int limit=-1, const std::string& before="", const std::string& after=""); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a49f52e..a2fda60 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -550,13 +550,36 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) 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); - } + std::string start; + std::string end; + const XmlNode* x = query->get_child("x", DATAFORM_NS); + if (x) + { + const XmlNode* value; + const auto fields = x->get_children("field", DATAFORM_NS); + for (const auto& field: fields) + { + if (field->get_tag("var") == "start") + { + value = field->get_child("value", DATAFORM_NS); + if (value) + start = value->get_inner(); + } + else if (field->get_tag("var") == "end") + { + value = field->get_child("value", DATAFORM_NS); + if (value) + end = value->get_inner(); + } + } + } + const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end); + 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; } -- cgit v1.2.3 From 2c6132f3ee8d29cd733019b34b0e6b8740a1e3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Sep 2016 23:11:42 +0200 Subject: Add 2 missing namespaces in our disco#info result --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a2fda60..246ea95 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -674,7 +674,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS}) + for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) { XmlNode feature("feature"); feature["var"] = ns; -- cgit v1.2.3 From 93ebf45f3050681f94497aac889d30885a503a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 20 Sep 2016 19:02:12 +0200 Subject: Make history messages work with fixed_irc_server fix #3209 --- src/bridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e24383e..2fc466a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -764,7 +764,7 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c for (const auto& line: lines) { const auto seconds = line.date.value().timeStamp(); - this->xmpp.send_history_message(chan_name + "%" + hostname, line.nick.value(), line.body.value(), + this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), line.body.value(), this->user_jid + "/" + resource, seconds); } #endif -- cgit v1.2.3 From 35fc5d6f290eeccd6d2f7267eed7355ed59d356e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 21 Sep 2016 20:38:14 +0200 Subject: Some little style/typo fixes --- src/bridge/bridge.cpp | 39 ++++++++++++++++++++++++--------------- src/xmpp/biboumi_component.cpp | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 2fc466a..d4c2b2a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -498,7 +498,8 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s Iid iid(nick, irc_hostname, Iid::Type::User); this->send_private_message(iid, "\01PING " + iq_id + "\01"); - irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid] + (const std::string& hostname, const IrcMessage& message) -> bool { if (irc_hostname != hostname || message.arguments.size() < 2) return false; @@ -586,7 +587,8 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std this->send_private_message(iid, "\01VERSION\01"); // TODO, add a timer to remove that waiting iq if the server does not // respond with a matching command before n seconds - irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid] + (const std::string& hostname, const IrcMessage& message) -> bool { if (irc_hostname != hostname) return false; @@ -659,15 +661,16 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } -void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource) +void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, + const std::string& resource) { if (!resource.empty()) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, - self); + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + resource, self); else for (const auto& res: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + res, - self); + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + res, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -730,7 +733,8 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan affiliation, role, this->user_jid + "/" + resource, self); } -void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who) +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, + const std::string& who) { for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) { @@ -764,7 +768,8 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c for (const auto& line: lines) { const auto seconds = line.date.value().timeStamp(); - this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), line.body.value(), + this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), + line.body.value(), this->user_jid + "/" + resource, seconds); } #endif @@ -775,7 +780,7 @@ std::string Bridge::get_own_nick(const Iid& iid) IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc) return irc->get_own_nick(); - return ""; + return {}; } size_t Bridge::active_clients() const @@ -791,8 +796,9 @@ void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::stri void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", ""); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) + this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, + "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) @@ -802,14 +808,16 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource); + this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, + this->user_jid + "/" + resource); } void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); + this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), + this->user_jid + "/" + *resources.begin()); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, @@ -821,7 +829,8 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Forward to the first resource (arbitrary, based on the “order” of the std::set) only const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); + this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), + this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 246ea95..fad87f9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -168,7 +168,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } else { - // An user wants to join an invalid IRC channel, return a presence error to him + // A user wants to join an invalid IRC channel, return a presence error to him/her if (type.empty()) this->send_invalid_room_error(to.local, to.resource, from_str); } -- cgit v1.2.3 From 07e2209596a1fbcc1d6f97d68bfda8e2add19ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 22 Sep 2016 00:00:13 +0200 Subject: Add the missing & for 3 std::string function arguments --- src/bridge/bridge.cpp | 2 +- src/bridge/bridge.hpp | 2 +- src/irc/iid.cpp | 2 +- src/irc/iid.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d4c2b2a..d16875f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -563,7 +563,7 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& "", true); } -void Bridge::send_irc_invitation(const Iid& iid, const std::string to) +void Bridge::send_irc_invitation(const Iid& iid, const std::string& to) { IrcClient* irc = this->get_irc_client(iid.get_server()); Jid to_jid(to); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 0e1fbf4..b278ea7 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -111,7 +111,7 @@ public: void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); - void send_irc_invitation(const Iid& iid, const std::string to); + void send_irc_invitation(const Iid& iid, const std::string& to); /*** ** diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index c951a49..ff14da5 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -5,7 +5,7 @@ #include -Iid::Iid(const std::string local, const std::string server, Iid::Type type): +Iid::Iid(const std::string& local, const std::string& server, Iid::Type type): type(type), local(local), server(server) diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 7361c51..a857ae9 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -58,7 +58,7 @@ public: Iid(const std::string& iid, const std::set& chantypes); Iid(const std::string& iid, const std::initializer_list& chantypes); Iid(const std::string& iid, const Bridge* bridge); - Iid(const std::string local, const std::string server, Type type); + Iid(const std::string& local, const std::string& server, Type type); Iid() = default; Iid(const Iid&) = default; -- cgit v1.2.3 From 2922c68e8315190bfc1aa81fbf4959dbb052be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 22 Sep 2016 19:01:31 +0200 Subject: Respond to disco#info requests on IRC server JIDs This makes it possible to execute an ad-hoc command on a server, with clients like Gajim, for example. --- src/xmpp/biboumi_component.cpp | 36 +++++++++++++++++++++++++++++++++++- src/xmpp/biboumi_component.hpp | 4 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index fad87f9..4a741f3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -399,9 +399,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) const XmlNode* query; if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info + Iid iid(to.local, {}); + const std::string node = query->get_tag("node"); if (to_str == this->served_hostname) { - const std::string node = query->get_tag("node"); if (node.empty()) { // On the gateway itself @@ -409,6 +410,14 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } + else if (iid.type == Iid::Type::Server) + { + if (node.empty()) + { + this->send_irc_server_disco_info(id, from, to_str); + stanza_error.disable(); + } + } } else if ((query = stanza.get_child("query", VERSION_NS))) { @@ -684,6 +693,31 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st this->send_stanza(iq); } +void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from) +{ + Jid from(jid_from); + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + XmlNode identity("identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "IRC server "s + from.local + " over Biboumi"; + query.add_child(std::move(identity)); + for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS}) + { + XmlNode feature("feature"); + feature["var"] = ns; + query.add_child(std::move(feature)); + } + iq.add_child(std::move(query)); + this->send_stanza(iq); +} + void BiboumiComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 1844451..8b2ac78 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -61,6 +61,10 @@ public: * Send a result IQ with the gateway disco informations. */ void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send a result IQ with the disco informations regarding IRC server JIDs. + */ + void send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); /** * Send an iq version request */ -- cgit v1.2.3 From 363a0bf02cf20592b2f9f034de1c5f54c8922b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 20:11:35 +0200 Subject: Look for uuid/uuid.h instead of just uuid.h Avoids a conflict between /usr/include/uuid.h and /usr/local/include/uuid/uuid.h on freebsd --- src/database/database.cpp | 2 +- src/xmpp/biboumi_component.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index e995d95..f7d309b 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4a741f3..c810ce3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #ifdef SYSTEMD_FOUND # include -- cgit v1.2.3 From ee4cf5dc2d3eaa43794a8ac736a6409e08082882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 20:53:40 +0200 Subject: Add AdhocCommandHandlers::add_command to simplify the usage of this class And make things a little bit clearer --- src/xmpp/biboumi_component.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index c810ce3..2b4ff18 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -56,35 +56,24 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->stanza_handlers.emplace("iq", std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1)); - this->adhoc_commands_handler.get_commands() = { - {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, - {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true)}, - {"disconnect-from-irc-servers", AdhocCommand({&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false)}, - {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} - }; + this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false}); + this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true}); + this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); + if (!Config::get("fixed_irc_server", "").empty()) - this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_server_command)); + this->adhoc_commands_handler.add_command("configure", configure_server_command); else - this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_global_command)); -#endif + this->adhoc_commands_handler.add_command("configure", configure_global_command); - this->irc_server_adhoc_commands_handler.get_commands() = { -#ifdef USE_DATABASE - {"configure", configure_server_command}, -#endif - }; - this->irc_channel_adhoc_commands_handler.get_commands() = { -#ifdef USE_DATABASE - {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)}, + this->irc_server_adhoc_commands_handler.add_command("configure", configure_server_command); + this->irc_channel_adhoc_commands_handler.add_command("configure", {{&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false}); #endif - }; } void BiboumiComponent::shutdown() -- cgit v1.2.3 From 265b5df61b5f3d4b5f30bbc6518f833f73bda1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 23:10:41 +0200 Subject: Re-add the ad-hoc command the was removed by mistake in the previous commit Thank you, e2e tests --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 2b4ff18..3a016b9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -59,7 +59,7 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false}); this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true}); - this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false}); this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); #ifdef USE_DATABASE -- cgit v1.2.3 From ffad4306b9e9c6065a01a5fcaca668d70af0db8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 2 Oct 2016 12:56:04 +0200 Subject: =?UTF-8?q?Use=20LIST=20*=20instead=20of=20just=20LIST,=20because?= =?UTF-8?q?=20some=20servers=20don=E2=80=99t=20accept=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a e2e test for the list query --- src/irc/irc_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 59da97e..85fdec5 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -428,7 +428,7 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin void IrcClient::send_list_command() { - this->send_message(IrcMessage("LIST", {})); + this->send_message(IrcMessage("LIST", {"*"})); } void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick) -- cgit v1.2.3 From 76a8189b46177eb78eee12d1cb3266f282acd380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 00:58:21 +0200 Subject: Implement result-set-management for LIST queries ref #2948 --- src/bridge/bridge.cpp | 181 +++++++++++++++++++++++++++++------ src/bridge/bridge.hpp | 26 ++++- src/bridge/list_element.hpp | 7 +- src/bridge/result_set_management.hpp | 10 ++ src/xmpp/biboumi_component.cpp | 62 ++++++++++-- src/xmpp/biboumi_component.hpp | 6 +- 6 files changed, 246 insertions(+), 46 deletions(-) create mode 100644 src/bridge/result_set_management.hpp (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d16875f..8849ef9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -10,6 +9,7 @@ #include #include #include +#include "result_set_management.hpp" using namespace std::string_literals; @@ -386,45 +386,164 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) irc->send_nick_command(new_nick); } -void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, - const std::string& to_jid) +void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, + ResultSetInfo rs_info) { - IrcClient* irc = this->get_irc_client(iid.get_server()); + auto& list = channel_list_cache[iid.get_server()]; - irc->send_list_command(); + // We fetch the list from the IRC server only if we have a complete + // cached list that needs to be invalidated (that is, when the request + // doesn’t have a after or before, or when the list is empty). + // If the list is not complete, this means that a request is already + // ongoing, so we just need to add the callback. + // By default the list is complete and empty. + if (list.complete && + (list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty()))) + { + IrcClient* irc = this->get_irc_client(iid.get_server()); + irc->send_list_command(); + + // Add a callback that will populate our list + list.channels.clear(); + list.complete = false; + irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname, + const IrcMessage& message) -> bool + { + if (irc_hostname != iid.get_server()) + return false; - std::vector list; + auto& list = channel_list_cache[iid.get_server()]; + + if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" + || message.command == "ERR_NOSUCHSERVER") + { + list.complete = true; + return true; + } + else if (message.command == "322" || message.command == "RPL_LIST") + { // Add element to list + if (message.arguments.size() == 4) + { + list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()), + message.arguments[2], message.arguments[3]); + } + return false; + } + else if (message.command == "323" || message.command == "RPL_LISTEND") + { // Send the iq response with the content of the list + list.complete = true; + return true; + } + return false; + }; - irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname, - const IrcMessage& message) mutable -> bool + this->add_waiting_irc(std::move(cb)); + } + + // If the list is complete, we immediately send the answer. + // Otherwise, we install a callback, that will populate our list and send + // the answer when we can. + if (list.complete) { - if (irc_hostname != iid.get_server()) + this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + } + else + { + // Add a callback to answer the request as soon as we can + irc_responder_callback_t cb = [this, iid, iq_id, to_jid, + rs_info=std::move(rs_info)](const std::string& irc_hostname, + const IrcMessage& message) -> bool + { + if (irc_hostname != iid.get_server()) + return false; + + if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" + || message.command == "ERR_NOSUCHSERVER") + { + std::string text; + if (message.arguments.size() >= 2) + text = message.arguments[1]; + this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false); + return true; + } + else if (message.command == "322" || message.command == "RPL_LIST") + { + auto& list = channel_list_cache[iid.get_server()]; + const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res); + return res; + } + else if (message.command == "323" || message.command == "RPL_LISTEND") + { // Send the iq response with the content of the list + auto& list = channel_list_cache[iid.get_server()]; + this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + return true; + } return false; - if (message.command == "263" || message.command == "RPL_TRYAGAIN" || - message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER") + }; + + this->add_waiting_irc(std::move(cb)); + } +} + +bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info, + const std::string& id, const std::string& to_jid, const std::string& from) +{ + auto begin = channel_list.channels.begin(); + auto end = channel_list.channels.begin(); + if (channel_list.complete) + { + begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (begin == channel_list.channels.end()) + begin = channel_list.channels.begin(); + else + begin = std::next(begin); + end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (rs_info.max >= 0) { - std::string text; - if (message.arguments.size() >= 2) - text = message.arguments[1]; - this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, - "wait", "service-unavailable", text, false); - return true; + if (std::distance(begin, end) >= rs_info.max) + end = begin + rs_info.max; } - else if (message.command == "322" || message.command == "RPL_LIST") - { // Add element to list - if (message.arguments.size() == 4) - list.emplace_back(message.arguments[1], message.arguments[2], - message.arguments[3]); - return false; + } + else + { + if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0) + return false; + if (!rs_info.after.empty()) + { + begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (begin == channel_list.channels.end()) + return false; + begin = std::next(begin); } - else if (message.command == "323" || message.command == "RPL_LISTEND") - { // Send the iq response with the content of the list - this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list); - return true; + if (!rs_info.before.empty()) + { + end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (end == channel_list.channels.end()) + return false; } - return false; - }; - this->add_waiting_irc(std::move(cb)); + if (rs_info.max >= 0) + { + if (std::distance(begin, end) < rs_info.max) + return false; + else + end = begin + rs_info.max; + } + } + this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info); + return true; } void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, @@ -1002,4 +1121,4 @@ void Bridge::set_record_history(const bool val) { this->record_history = val; } -#endif \ No newline at end of file +#endif diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b278ea7..1a1d201 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -17,6 +19,7 @@ class BiboumiComponent; class Poller; +class ResultSetInfo; /** * A callback called for each IrcMessage we receive. If the message triggers @@ -87,8 +90,19 @@ public: void send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); - void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, - const std::string& to_jid); + void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, + ResultSetInfo rs_info); + /** + * Check if the channel list contains what is needed to answer the RSM request, + * if it does, send the iq result. If the list is complete but does not contain + * everything, send the result anyway (because there are no more available + * channels that could complete the list). + * + * Returns true if we sent the answer. + */ + bool send_matching_channel_list(const ChannelList& channel_list, + const ResultSetInfo& rs_info, const std::string& id, const std::string& to_jid, + const std::string& from); void forward_affiliation_role_change(const Iid& iid, const std::string& nick, const std::string& affiliation, const std::string& role); /** @@ -271,7 +285,6 @@ private: * response iq. */ std::vector waiting_irc; - /** * Resources to IRC channel/server mapping: */ @@ -300,6 +313,13 @@ private: * TODO: send message history */ void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); + /** + * A cache of the channels list (as returned by the server on a LIST + * request), to be re-used on a subsequent XMPP list request that + * uses result-set-management. + */ + std::map channel_list_cache; + #ifdef USE_DATABASE bool record_history { true }; #endif diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp index 1eff2ee..554c83d 100644 --- a/src/bridge/list_element.hpp +++ b/src/bridge/list_element.hpp @@ -1,6 +1,6 @@ #pragma once - +#include #include struct ListElement @@ -17,3 +17,8 @@ struct ListElement }; +struct ChannelList +{ + bool complete{true}; + std::vector channels{}; +}; diff --git a/src/bridge/result_set_management.hpp b/src/bridge/result_set_management.hpp new file mode 100644 index 0000000..6ff82ba --- /dev/null +++ b/src/bridge/result_set_management.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct ResultSetInfo +{ + int max{-1}; + std::string before{}; + std::string after{}; +}; diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 3a016b9..f43b5e0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -27,6 +27,7 @@ #endif #include +#include using namespace std::string_literals; @@ -463,7 +464,22 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if (node.empty() && iid.type == Iid::Type::Server) { // Disco on an IRC server: get the list of channels - bridge->send_irc_channel_list_request(iid, id, from); + ResultSetInfo rs_info; + const XmlNode* set_node = query->get_child("set", RSM_NS); + if (set_node) + { + const XmlNode* after = set_node->get_child("after", RSM_NS); + if (after) + rs_info.after = after->get_inner(); + const XmlNode* before = set_node->get_child("before", RSM_NS); + if (before) + rs_info.before = before->get_inner(); + const XmlNode* max = set_node->get_child("max", RSM_NS); + if (max) + rs_info.max = std::atoi(max->get_inner().data()); + + } + bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info)); stanza_error.disable(); } } @@ -749,10 +765,11 @@ void BiboumiComponent::send_ping_request(const std::string& from, this->waiting_iq[id] = result_cb; } -void BiboumiComponent::send_iq_room_list_result(const std::string& id, - const std::string& to_jid, - const std::string& from, - const std::vector& rooms_list) +void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std::string& to_jid, + const std::string& from, const ChannelList& channel_list, + std::vector::const_iterator begin, + std::vector::const_iterator end, + const ResultSetInfo& rs_info) { Stanza iq("iq"); iq["from"] = from + "@" + this->served_hostname; @@ -761,12 +778,41 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, iq["type"] = "result"; XmlNode query("query"); query["xmlns"] = DISCO_ITEMS_NS; - for (const auto& room: rooms_list) + + for (auto it = begin; it != end; ++it) { XmlNode item("item"); - item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; + item["jid"] = it->channel + "@" + this->served_hostname; query.add_child(std::move(item)); } + + if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) + { + XmlNode set_node("set"); + set_node["xmlns"] = RSM_NS; + + if (begin != channel_list.channels.cend()) + { + XmlNode first_node("first"); + first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin)); + first_node.set_inner(begin->channel + "@" + this->served_hostname); + set_node.add_child(std::move(first_node)); + } + if (end != channel_list.channels.cbegin()) + { + XmlNode last_node("last"); + last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname); + set_node.add_child(std::move(last_node)); + } + if (channel_list.complete) + { + XmlNode count_node("count"); + count_node.set_inner(std::to_string(channel_list.channels.size())); + set_node.add_child(std::move(count_node)); + } + query.add_child(std::move(set_node)); + } + iq.add_child(std::move(query)); this->send_stanza(iq); } diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 8b2ac78..77104ed 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -79,9 +79,9 @@ public: /** * Send the channels list in one big stanza */ - void send_iq_room_list_result(const std::string& id, const std::string& to_jid, - const std::string& from, - const std::vector& rooms_list); + void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from, + const ChannelList& channel_list, std::vector::const_iterator begin, + std::vector::const_iterator end, const ResultSetInfo& rs_info); void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); /** * Handle the various stanza types -- cgit v1.2.3 From 729078a238e563d071f7905e01d3029a880fd479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 01:11:54 +0200 Subject: Add missing include file --- src/bridge/bridge.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8849ef9..65171a9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -10,6 +10,7 @@ #include #include #include "result_set_management.hpp" +#include using namespace std::string_literals; -- cgit v1.2.3 From b29225601a475efe7f28fe7002eba72e70f3272b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Oct 2016 02:54:35 +0200 Subject: Fix some compilation warning/errors that appear on FreeBSD --- src/bridge/bridge.hpp | 2 +- src/irc/iid.cpp | 2 ++ src/irc/iid.hpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1a1d201..208de32 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -19,7 +19,7 @@ class BiboumiComponent; class Poller; -class ResultSetInfo; +struct ResultSetInfo; /** * A callback called for each IrcMessage we receive. If the message triggers diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index ff14da5..d442013 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -5,6 +5,8 @@ #include +constexpr char Iid::separator[]; + Iid::Iid(const std::string& local, const std::string& server, Iid::Type type): type(type), local(local), diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index a857ae9..44861c1 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -54,7 +54,7 @@ public: User, Server, }; - static constexpr auto separator = "%"; + static constexpr char separator[]{"%"}; Iid(const std::string& iid, const std::set& chantypes); Iid(const std::string& iid, const std::initializer_list& chantypes); Iid(const std::string& iid, const Bridge* bridge); -- cgit v1.2.3 From 28f1dd76548fc9a7de3920d938903f68cdfffe0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 21:35:16 +0200 Subject: Make version requests work with global user JIDs as well fix #3210 --- src/bridge/bridge.cpp | 2 +- src/xmpp/biboumi_component.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 65171a9..7724ba7 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -713,7 +713,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std if (irc_hostname != hostname) return false; IrcUser user(message.prefix); - if (message.command == "NOTICE" && user.nick == target && + if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) && message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ") { // remove the "\01VERSION " and the "\01" parts from the string diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f43b5e0..1e66b62 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -412,7 +412,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) else if ((query = stanza.get_child("query", VERSION_NS))) { Iid iid(to.local, bridge); - if (iid.type != Iid::Type::Server && !to.resource.empty()) + if ((iid.type == Iid::Type::Channel && !to.resource.empty()) || + (iid.type == Iid::Type::User)) { // Get the IRC user version std::string target; -- cgit v1.2.3 From 1d197ff26ce5a88ba851969edb3ea915759c3477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Oct 2016 19:32:45 +0200 Subject: Respond to muc#traffic requests fix #3069 --- src/xmpp/biboumi_component.cpp | 26 ++++++++++++++++++++++++++ src/xmpp/biboumi_component.hpp | 5 +++++ 2 files changed, 31 insertions(+) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 1e66b62..b9a8779 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -408,6 +408,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } + else if (iid.type == Iid::Type::Channel) + { + if (node == MUC_TRAFFIC_NS) + { + this->send_irc_channel_muc_traffic_info(id, from, to_str); + } + } } else if ((query = stanza.get_child("query", VERSION_NS))) { @@ -724,6 +731,25 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s this->send_stanza(iq); } +void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["from"] = jid_from; + iq["to"] = jid_to; + + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + query["node"] = MUC_TRAFFIC_NS; + // We drop all “special” traffic (like xhtml-im, chatstates, etc), so + // don’t include any + iq.add_child(std::move(iq)); + + this->send_stanza(iq); + +} + void BiboumiComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 77104ed..d5b87e9 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -65,6 +65,11 @@ public: * Send a result IQ with the disco informations regarding IRC server JIDs. */ void send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); + /** + * Sends the allowed namespaces in MUC message, according to + * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic + */ + void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to); /** * Send an iq version request */ -- cgit v1.2.3 From 3620d533ee88a8804317d2745320c0186192ddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 5 Oct 2016 00:17:22 +0200 Subject: Avoid sending PART command for unjoined channels fix #3205 --- src/bridge/bridge.cpp | 10 +++++++++- src/irc/irc_channel.cpp | 6 ------ src/irc/irc_channel.hpp | 15 +++++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7724ba7..fb7ea42 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -360,7 +360,15 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) { - irc->send_part_command(iid.get_local(), status_message); + // Do not send a PART message if we actually are not in that channel + // or if we already sent a PART but we are just waiting for the + // acknowledgment from the server + IrcChannel* channel = irc->get_channel(iid.get_local()); + if (channel->joined && !channel->parting) + { + irc->send_part_command(iid.get_local(), status_message); + channel->parting = true; + } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index e769245..40d7f54 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -1,12 +1,6 @@ #include #include -IrcChannel::IrcChannel(): - joined(false), - self(nullptr) -{ -} - void IrcChannel::set_self(const std::string& name) { this->self = std::make_unique(name); diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 2bcefaf..7c269b9 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -14,16 +14,19 @@ class IrcChannel { public: - explicit IrcChannel(); + IrcChannel() = default; IrcChannel(const IrcChannel&) = delete; IrcChannel(IrcChannel&&) = delete; IrcChannel& operator=(const IrcChannel&) = delete; IrcChannel& operator=(IrcChannel&&) = delete; - bool joined; - std::string topic; - std::string topic_author; + bool joined{false}; + // Set to true if we sent a PART but didn’t yet receive the PART ack from + // the server + bool parting{false}; + std::string topic{}; + std::string topic_author{}; void set_self(const std::string& name); IrcUser* get_self() const; IrcUser* add_user(const std::string& name, @@ -35,8 +38,8 @@ public: { return this->users; } protected: - std::unique_ptr self; - std::vector> users; + std::unique_ptr self{}; + std::vector> users{}; }; /** -- cgit v1.2.3 From 0049b3e32d1d65acb4314208ddfdd52728d17162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 22:23:04 +0200 Subject: Remove a potential nullptr dereference, on mam queryid fix coverity CID 153376 --- src/xmpp/biboumi_component.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index b9a8779..49a1fd5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -598,9 +598,8 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end); 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_archived_message(line, to.full(), from.full(), query_id); } this->send_iq_result_full_jid(id, from.full(), to.full()); return true; @@ -617,7 +616,8 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con XmlNode result("result"); result["xmlns"] = MAM_NS; - result["queryid"] = queryid; + if (!queryid.empty()) + result["queryid"] = queryid; result["id"] = log_line.uuid.value(); XmlNode forwarded("forwarded"); -- cgit v1.2.3 From 8ac8d2b2425d19eb995a36efa808b664979e358f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:28:40 +0200 Subject: Correctly set status="110" in the presence for the target of a kick --- src/bridge/bridge.cpp | 5 +++-- src/bridge/bridge.hpp | 3 ++- src/irc/irc_client.cpp | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index fb7ea42..ab42876 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -916,10 +916,11 @@ size_t Bridge::active_clients() const return this->irc_clients.size(); } -void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) +void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author, + const bool self) { for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource); + this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource, self); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 208de32..b2432f0 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -181,7 +181,8 @@ public: const std::string& new_nick, const char user_mode, const bool self); - void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); + void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author, + const bool self); void send_nickname_conflict_error(const Iid& iid, const std::string& nickname); /** * Send a role/affiliation change, matching the change of mode for that user diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 85fdec5..c301af0 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -964,14 +964,15 @@ void IrcClient::on_kick(const IrcMessage& message) IrcChannel* channel = this->get_channel(chan_name); if (!channel->joined) return ; - if (channel->get_self()->nick == target) + const bool self = channel->get_self()->nick == target; + if (self) channel->joined = false; IrcUser author(message.prefix); Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick); + this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick, self); } void IrcClient::on_invite(const IrcMessage& message) -- cgit v1.2.3 From 116472920ce4bcd4c9512db67108cdbf9895e8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:29:14 +0200 Subject: Fix the muc#traffic response Was completely broken, and the test was just useless --- src/xmpp/biboumi_component.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 49a1fd5..86bef2d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -389,7 +389,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) const XmlNode* query; if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info - Iid iid(to.local, {}); + Iid iid(to.local, {'#', '&'}); const std::string node = query->get_tag("node"); if (to_str == this->served_hostname) { @@ -413,6 +413,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (node == MUC_TRAFFIC_NS) { this->send_irc_channel_muc_traffic_info(id, from, to_str); + stanza_error.disable(); } } } @@ -744,7 +745,7 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, c query["node"] = MUC_TRAFFIC_NS; // We drop all “special” traffic (like xhtml-im, chatstates, etc), so // don’t include any - iq.add_child(std::move(iq)); + iq.add_child(std::move(query)); this->send_stanza(iq); -- cgit v1.2.3 From dfc0793ef2fec12d2613b53b27f1a7f85dae2688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 11 Oct 2016 00:43:46 +0200 Subject: Include a private and no-copy nodes in private to avoid carbon duplication --- src/bridge/bridge.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ab42876..9300d45 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -771,13 +771,13 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto chan_name = Iid(Jid(it->second).local, {}).get_local(); for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}]) this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding), - this->user_jid + "/" + resource, "chat", true); + this->user_jid + "/" + resource, "chat", true, true); } else { for (const auto& resource: this->resources_in_server[iid.get_server()]) this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding), - this->user_jid + "/" + resource, "chat", false); + this->user_jid + "/" + resource, "chat", false, true); } } } @@ -835,7 +835,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho const auto encoding = in_encoding_for(*this, {from, this}); for (const auto& resource: this->resources_in_server[from]) { - this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat"); + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); } } -- cgit v1.2.3 From 00eb18bae8cf62d49f4b5d42aed8507fcca3c03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 23 Oct 2016 23:58:03 +0200 Subject: Refactor channel->parting to a new location --- src/bridge/bridge.cpp | 5 +---- src/irc/irc_client.cpp | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9300d45..ac69ebc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -365,10 +365,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con // acknowledgment from the server IrcChannel* channel = irc->get_channel(iid.get_local()); if (channel->joined && !channel->parting) - { - irc->send_part_command(iid.get_local(), status_message); - channel->parting = true; - } + irc->send_part_command(iid.get_local(), status_message); // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index c301af0..9877806 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -502,6 +502,7 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin this->leave_dummy_channel(status_message); else this->send_message(IrcMessage("PART", {chan_name, status_message})); + channel->parting = true; } } -- cgit v1.2.3 From c3bb9fe2e2c2a0b2773e9b9824c4e675448b862f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 23 Oct 2016 23:59:21 +0200 Subject: Handle forced-join by just sending an invitation fix #3116 --- src/bridge/bridge.cpp | 15 +++++++++++++-- src/xmpp/biboumi_component.cpp | 5 ++++- 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ac69ebc..67eb805 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -839,8 +839,19 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const IrcUser* user, const char user_mode, const bool self) { - for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) - this->send_user_join(hostname, chan_name, user, user_mode, self, resource); + const auto resources = this->resources_in_chan[ChannelKey{chan_name, hostname}]; + if (self && resources.empty()) + { // This was a forced join: no client ever asked to join this room, + // but the server tells us we are in that room anyway. XMPP can’t + // do that, so we invite all the resources to join that channel. + const Iid iid(chan_name, hostname, Iid::Type::Channel); + this->send_xmpp_invitation(iid, ""); + } + else + { + for (const auto& resource: resources) + this->send_user_join(hostname, chan_name, user, user_mode, self, resource); + } } void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 86bef2d..f3405df 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -855,7 +855,10 @@ void BiboumiComponent::send_invitation(const std::string& room_target, XmlNode x("x"); x["xmlns"] = MUC_USER_NS; XmlNode invite("invite"); - invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + if (author_nick.empty()) + invite["from"] = room_target + "@" + this->served_hostname; + else + invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; x.add_child(std::move(invite)); message.add_child(std::move(x)); this->send_stanza(message); -- cgit v1.2.3 From 5990a8bf8ae622f075a7e2a12b2f5ac0cab8713b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 00:18:34 +0200 Subject: Correctly handle the nick change inside the virtual channel --- src/irc/irc_client.cpp | 51 +++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 9877806..6a87b99 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -931,29 +931,38 @@ void IrcClient::on_quit(const IrcMessage& message) void IrcClient::on_nick(const IrcMessage& message) { - const std::string new_nick = message.arguments[0]; + const std::string new_nick = IrcUser(message.arguments[0]).nick; + const std::string current_nick = IrcUser(message.prefix).nick; + const auto change_nick_func = [&](const std::string& chan_name, const IrcChannel* channel) + { + IrcUser* user; + if (channel->get_self() && channel->get_self()->nick == current_nick) + user = channel->get_self(); + else + user = channel->find_user(current_nick); + if (user) + { + std::string old_nick = user->nick; + Iid iid(chan_name, this->hostname, Iid::Type::Channel); + const bool self = channel->get_self()->nick == old_nick; + const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); + this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); + user->nick = new_nick; + if (self) + { + channel->get_self()->nick = new_nick; + this->current_nick = new_nick; + } + } + }; + + if (this->get_dummy_channel().joined) + { + change_nick_func("", &this->get_dummy_channel()); + } for (auto it = this->channels.begin(); it != this->channels.end(); ++it) { - const std::string chan_name = it->first; - IrcChannel* channel = it->second.get(); - IrcUser* user = channel->find_user(message.prefix); - if (user) - { - std::string old_nick = user->nick; - Iid iid; - iid.set_local(chan_name); - iid.set_server(this->hostname); - iid.type = Iid::Type::Channel; - const bool self = channel->get_self()->nick == old_nick; - const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); - this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); - user->nick = new_nick; - if (self) - { - channel->get_self()->nick = new_nick; - this->current_nick = new_nick; - } - } + change_nick_func(it->first, it->second.get()); } } -- cgit v1.2.3 From 1d2f20600a937cd00181a2fad055cd9bb56f058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:38:26 +0100 Subject: Handle IRC message 341 (invite confirmation) --- src/irc/irc_client.cpp | 9 +++++++++ src/irc/irc_client.hpp | 4 ++++ 2 files changed, 13 insertions(+) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6a87b99..033671e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -46,6 +46,7 @@ static const std::unordered_mapmotd.erase(); } +void IrcClient::on_invited(const IrcMessage& message) +{ + const std::string& chan_name = message.arguments[2]; + const std::string& invited_nick = message.arguments[1]; + + this->bridge.send_xmpp_message(this->hostname, "", invited_nick + " has been invited to " + chan_name); +} + void IrcClient::on_empty_topic(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 9f53c3c..1b4d892 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -213,6 +213,10 @@ public: * Empty the topic */ void on_empty_topic(const IrcMessage& message); + /** + * The IRC server is confirming that the invitation has been forwarded + */ + void on_invited(const IrcMessage& message); /** * The channel has been completely joined (self presence, topic, all names * received etc), send the self presence and topic to the XMPP user. -- cgit v1.2.3 From ae02e58b9dc276b247be84e1d708ca50a1f5bbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 13:54:25 +0100 Subject: Some cleanups --- src/irc/irc_client.cpp | 2 +- src/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 033671e..b0d3a47 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -942,7 +942,7 @@ void IrcClient::on_nick(const IrcMessage& message) { const std::string new_nick = IrcUser(message.arguments[0]).nick; const std::string current_nick = IrcUser(message.prefix).nick; - const auto change_nick_func = [&](const std::string& chan_name, const IrcChannel* channel) + const auto change_nick_func = [this, &new_nick, ¤t_nick](const std::string& chan_name, const IrcChannel* channel) { IrcUser* user; if (channel->get_self() && channel->get_self()->nick == current_nick) diff --git a/src/main.cpp b/src/main.cpp index 53f3193..5585672 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,9 +14,9 @@ #include // A flag set by the SIGINT signal handler. -static volatile std::atomic stop(false); +static std::atomic stop(false); // Flag set by the SIGUSR1/2 signal handler. -static volatile std::atomic reload(false); +static std::atomic reload(false); // A flag indicating that we are wanting to exit the process. i.e: if this // flag is set and all connections are closed, we can exit properly. static bool exiting = false; -- cgit v1.2.3 From e1934a0b5fb1589bcdd85b86b315d652488d0577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 1 Nov 2016 17:38:59 +0100 Subject: Disable e2e tests on openbsd --- src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 5585672..019dff0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include +#include // A flag set by the SIGINT signal handler. static std::atomic stop(false); @@ -84,7 +85,7 @@ int main(int ac, char** av) try { open_database(); - } catch (...) { + } catch (const litesql::DatabaseError&) { return 1; } -- cgit v1.2.3 From c70301e503fdb887387a54fcf5284d593e65d837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Nov 2016 21:25:26 +0100 Subject: Fix the presences sent, when multiple resources join the virtual channel fix #3216 --- src/bridge/bridge.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 67eb805..a0ecc6e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -177,16 +177,23 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const { // Join the dummy channel if (irc->is_welcomed()) { - if (irc->get_dummy_channel().joined) + if (res_in_chan) return false; // Immediately simulate a message coming from the IRC server saying that we // joined the channel - const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); - irc->on_channel_join(join_message); - const IrcMessage end_join_message(std::string(iid.get_server()), "366", - {irc->get_nick(), - "", "End of NAMES list"}); - irc->on_channel_completely_joined(end_join_message); + if (irc->get_dummy_channel().joined) + { + this->generate_channel_join_for_resource(iid, resource); + } + else + { + const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); + irc->on_channel_join(join_message); + const IrcMessage end_join_message(std::string(iid.get_server()), "366", + {irc->get_nick(), + "", "End of NAMES list"}); + irc->on_channel_completely_joined(end_join_message); + } } else { -- cgit v1.2.3 From 7376831bc8f6dbec8eaf4f4c0a6bba819a0a1e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:43:07 +0100 Subject: Add get-irc-connection-info adhoc command fix #3171 --- src/bridge/bridge.hpp | 4 +++ src/xmpp/biboumi_adhoc_commands.cpp | 67 ++++++++++++++++++++++++++++++++++++- src/xmpp/biboumi_adhoc_commands.hpp | 2 ++ src/xmpp/biboumi_component.cpp | 6 ++++ src/xmpp/biboumi_component.hpp | 2 +- 5 files changed, 79 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b2432f0..18ebfeb 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -248,10 +248,12 @@ private: * a IRCServerNotConnected error in that case. */ IrcClient* get_irc_client(const std::string& hostname); +public: /** * Idem, but returns nullptr if the server does not exist. */ IrcClient* find_irc_client(const std::string& hostname) const; +private: /** * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. @@ -293,7 +295,9 @@ private: using ChannelName = std::string; using IrcHostname = std::string; using ChannelKey = std::tuple; +public: std::map> resources_in_chan; +private: std::map> resources_in_server; /** * Manage which resource is in which channel diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index af7e473..87b8d96 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,10 +1,12 @@ #include #include +#include +#include #include #include #include #include -#include +#include #include @@ -720,3 +722,66 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& note.set_inner(msg); command_node.add_child(std::move(note)); } + +void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node) +{ + BiboumiComponent& biboumi_component = static_cast(component); + + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + std::string message{}; + + // As the function is exited, set the message in the response. + utils::ScopeGuard sg([&message, &command_node]() + { + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner(message); + command_node.add_child(std::move(note)); + }); + + Bridge* bridge = biboumi_component.get_user_bridge(owner.bare()); + if (!bridge) + { + message = "You are not connected to anything."; + return; + } + + std::string hostname; + if ((hostname = Config::get("fixed_irc_server", "")).empty()) + hostname = target.local; + + IrcClient* irc = bridge->find_irc_client(hostname); + if (!irc || !irc->is_connected()) + { + message = "You are not connected to the IRC server "s + hostname; + return; + } + + std::ostringstream ss; + ss << "Connected to IRC server " << irc->get_hostname() << " on port " << irc->get_port(); + if (irc->is_using_tls()) + ss << " (using TLS)"; + const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date); + ss << " since " << std::put_time(std::localtime(&now_c), "%F %T"); + ss << " (" << std::chrono::duration_cast(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago)."; + + for (const auto& it: bridge->resources_in_chan) + { + const auto& channel_key = it.first; + const auto& irc_hostname = std::get<1>(channel_key); + const auto& resources = it.second; + + if (irc_hostname == irc->get_hostname() && !resources.empty()) + { + const auto& channel_name = std::get<0>(channel_key); + ss << "\n" << channel_name << " from " << resources.size() << " resource" << (resources.size() > 1 ? "s": "") << ": "; + for (const auto& resource: resources) + ss << resource << " "; + } + } + + message = ss.str(); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 7be5509..b5fce61 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -22,3 +22,5 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); + +void GetIrcConnectionInfoStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f3405df..4398562 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -63,6 +63,12 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false}); this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); + AdhocCommand get_irc_connection_info{{&GetIrcConnectionInfoStep1}, "Returns various information about your connection to this IRC server.", false}; + if (!Config::get("fixed_irc_server", "").empty()) + this->adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + else + this->irc_server_adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index d5b87e9..999001f 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -101,13 +101,13 @@ public: const std::string& queryid); #endif -private: /** * Return the bridge associated with the bare JID. Create a new one * if none already exist. */ Bridge* get_user_bridge(const std::string& user_jid); +private: /** * A map of id -> callback. When we want to wait for an iq result, we add * the callback to this map, with the iq id as the key. When an iq result -- cgit v1.2.3 From 962bac476cc69362b748675db58ea6eb364501e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:43:52 +0100 Subject: Trivial refactor of get_user_bridge function --- src/xmpp/biboumi_component.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4398562..010b415 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -663,8 +663,7 @@ Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) } catch (const std::out_of_range& exception) { - this->bridges.emplace(bare_jid, std::make_unique(bare_jid, *this, this->poller)); - return this->bridges.at(bare_jid).get(); + return this->bridges.emplace(bare_jid, std::make_unique(bare_jid, *this, this->poller)).first->second.get(); } } -- cgit v1.2.3 From 597cfce4b88f9318ed8b580e8b26df9be8fc637a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:52:08 +0100 Subject: Add back --- src/xmpp/biboumi_adhoc_commands.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 87b8d96..1349a66 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From a52baa52e25c9767d1be95a10b2a56334aaeb471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 20:26:28 +0100 Subject: Workaround for debian and other old OS that do not have std::put_time --- src/xmpp/biboumi_adhoc_commands.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 1349a66..003b901 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -15,6 +15,10 @@ #include #endif +#ifndef HAS_PUT_TIME +#include +#endif + using namespace std::string_literals; void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) @@ -766,7 +770,15 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, if (irc->is_using_tls()) ss << " (using TLS)"; const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date); +#ifdef HAS_PUT_TIME ss << " since " << std::put_time(std::localtime(&now_c), "%F %T"); +#else + constexpr std::size_t timestamp_size{10 + 1 + 8 + 1}; + char buf[timestamp_size] = {}; + const auto res = std::strftime(buf, timestamp_size, "%F %T", std::localtime(&now_c)); + if (res > 0) + ss << " since " << buf; +#endif ss << " (" << std::chrono::duration_cast(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago)."; for (const auto& it: bridge->resources_in_chan) -- cgit v1.2.3 From b5beb043325ca5625f4eb53cb9451daf499c586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 21:57:39 +0100 Subject: Remove a never reached (and non-sensical) error --- src/xmpp/biboumi_component.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 010b415..d6782e2 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -269,8 +269,6 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } } - else if (iid.type == Iid::Type::User) - this->send_invalid_user_error(to.local, from_str); } catch (const IRCNotConnected& ex) { this->send_stanza_error("message", from_str, to_str, id, -- cgit v1.2.3