summaryrefslogtreecommitdiff
path: root/src/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'src/bridge')
-rw-r--r--src/bridge/bridge.cpp186
-rw-r--r--src/bridge/bridge.hpp26
-rw-r--r--src/bridge/colors.hpp2
3 files changed, 170 insertions, 44 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index a0ecc6e..0d6ade3 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -1,4 +1,5 @@
#include <bridge/bridge.hpp>
+#include <utility>
#include <xmpp/biboumi_component.hpp>
#include <network/poller.hpp>
#include <utils/empty_if_fixed_server.hpp>
@@ -11,6 +12,7 @@
#include <database/database.hpp>
#include "result_set_management.hpp"
#include <algorithm>
+#include <utils/timed_events.hpp>
using namespace std::string_literals;
@@ -28,8 +30,8 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
#endif
}
-Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller):
- user_jid(user_jid),
+Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
+ user_jid(std::move(user_jid)),
xmpp(xmpp),
poller(poller)
{
@@ -58,10 +60,10 @@ static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(c
void Bridge::shutdown(const std::string& exit_message)
{
- for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it)
+ for (auto& pair: this->irc_clients)
{
- it->second->send_quit_command(exit_message);
- it->second->leave_dummy_channel(exit_message);
+ pair.second->send_quit_command(exit_message);
+ pair.second->leave_dummy_channel(exit_message, {});
}
}
@@ -167,7 +169,8 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
const std::string& resource)
{
- const auto hostname = iid.get_server();
+ const auto& hostname = iid.get_server();
+ this->cancel_linger_timer(hostname);
IrcClient* irc = this->make_irc_client(hostname, nickname);
this->add_resource_to_server(hostname, resource);
auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
@@ -263,9 +266,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
}
}
-void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& nick,
+void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& from,
+ const std::string& nick,
const std::string& affiliation,
- const std::string& role)
+ const std::string& role,
+ const std::string& id)
{
IrcClient* irc = this->get_irc_client(iid.get_server());
IrcChannel* chan = irc->get_channel(iid.get_local());
@@ -273,7 +278,11 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string&
return;
IrcUser* user = chan->find_user(nick);
if (!user)
- return;
+ {
+ this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel",
+ "item-not-found", "no such nick", false);
+ return;
+ }
// For each affiliation or role, we have a “maximal” mode that we want to
// set. We must remove any superior mode at the same time. For example if
// the user already has +o mode, and we set its affiliation to member, we
@@ -325,6 +334,56 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string&
std::vector<std::string> args(nb, nick);
args.insert(args.begin(), modes);
irc->send_mode_command(iid.get_local(), args);
+
+ irc_responder_callback_t cb = [this, iid, irc, id, from, nick](const std::string& irc_hostname, const IrcMessage& message) -> bool
+ {
+ if (irc_hostname != iid.get_server())
+ return false;
+
+ if (message.command == "MODE" && message.arguments.size() >= 2)
+ {
+ const std::string& chan_name = message.arguments[0];
+ if (chan_name != iid.get_local())
+ return false;
+ const std::string actor_nick = IrcUser{message.prefix}.nick;
+ if (!irc || irc->get_own_nick() != actor_nick)
+ return false;
+
+ this->xmpp.send_iq_result(id, from, std::to_string(iid));
+ }
+ else if (message.command == "401" && message.arguments.size() >= 2)
+ {
+ const std::string target_later = message.arguments[1];
+ if (target_later != nick)
+ return false;
+ std::string error_message = "No such nick";
+ if (message.arguments.size() >= 3)
+ error_message = message.arguments[2];
+ this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "item-not-found",
+ error_message, false);
+ }
+ else if (message.command == "482" && message.arguments.size() >= 2)
+ {
+ const std::string chan_name_later = utils::tolower(message.arguments[1]);
+ if (chan_name_later != iid.get_local())
+ return false;
+ std::string error_message = "You're not channel operator";
+ if (message.arguments.size() >= 3)
+ error_message = message.arguments[2];
+ this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
+ error_message, false);
+ }
+ else if (message.command == "472" && message.arguments.size() >= 2)
+ {
+ std::string error_message = "Unknown mode: "s + message.arguments[1];
+ if (message.arguments.size() >= 3)
+ error_message = message.arguments[2];
+ this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
+ error_message, false);
+ }
+ return true;
+ };
+ this->add_waiting_irc(std::move(cb));
}
void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
@@ -364,37 +423,56 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
if (!this->is_resource_in_chan(key, resource))
return ;
+ IrcChannel* channel = irc->get_channel(iid.get_local());
+ auto nick = channel->get_self()->nick;
+
const auto resources = this->number_of_resources_in_chan(key);
if (resources == 1)
{
// 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);
+ bool persistent = false;
+#ifdef USE_DATABASE
+ const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
+ iid.get_server(), iid.get_local());
+ persistent = coptions.persistent.value();
+#endif
+ if (channel->joined && !channel->parting && !persistent)
+ {
+ const auto& chan_name = iid.get_local();
+ if (chan_name.empty())
+ irc->leave_dummy_channel(status_message, resource);
+ else
+ irc->send_part_command(iid.get_local(), status_message);
+ }
+ else
+ {
+ this->send_muc_leave(iid, std::move(nick), "", true, resource);
+ }
// 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());
}
else
{
- IrcChannel* chan = irc->get_channel(iid.get_local());
- if (chan)
- {
- auto nick = chan->get_self()->nick;
- this->remove_resource_from_chan(key, resource);
- this->send_muc_leave(std::move(iid), std::move(nick),
- "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.",
- true, resource);
- if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
- this->remove_resource_from_server(iid.get_server(), resource);
- }
+ if (channel)
+ this->send_muc_leave(iid, std::move(nick),
+ "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.",
+ true, resource);
+ this->remove_resource_from_chan(key, resource);
+ if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
+ this->remove_resource_from_server(iid.get_server(), resource);
}
+
}
-void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick)
+void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
{
+ // We don’t change the nick if the presence was sent to a channel the resource is not in.
+ auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), iid.get_server()}, requesting_resource);
+ if (!res_in_chan)
+ return;
IrcClient* irc = this->get_irc_client(iid.get_server());
irc->send_nick_command(new_nick);
}
@@ -793,19 +871,24 @@ 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,
+void Bridge::send_muc_leave(const 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->xmpp.send_muc_leave(std::to_string(iid), 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);
+ {
+ for (const auto &res: this->resources_in_chan[iid.to_tuple()])
+ this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message),
+ this->user_jid + "/" + res, self);
+ if (self)
+ this->remove_all_resources_from_chan(iid.to_tuple());
+
+ }
IrcClient* irc = this->find_irc_client(iid.get_server());
- if (irc && irc->number_of_joined_channels() == 0)
- irc->send_quit_command("");
+ if (self && irc && irc->number_of_joined_channels() == 0)
+ this->quit_or_start_linger_timer(iid.get_server());
}
void Bridge::send_nick_change(Iid&& iid,
@@ -968,7 +1051,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string&
const std::string& id)
{
// Use revstr because the forwarded ping to target XMPP user must not be
- // the same that the request iq, but we also need to get it back easily
+ // the same as the request iq, but we also need to get it back easily
// (revstr again)
// Forward to the first resource (arbitrary, based on the “order” of the std::set) only
const auto resources = this->resources_in_server[hostname];
@@ -1033,6 +1116,11 @@ std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_cli
return this->irc_clients;
}
+const std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients() const
+{
+ return this->irc_clients;
+}
+
std::set<char> Bridge::get_chantypes(const std::string& hostname) const
{
IrcClient* irc = this->find_irc_client(hostname);
@@ -1070,6 +1158,11 @@ bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::s
return false;
}
+void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel)
+{
+ this->resources_in_chan.erase(channel);
+}
+
void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
{
auto it = this->resources_in_server.find(irc_hostname);
@@ -1099,9 +1192,9 @@ bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, cons
return false;
}
-std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel_key) const
+std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel) const
{
- auto it = this->resources_in_chan.find(channel_key);
+ auto it = this->resources_in_chan.find(channel);
if (it == this->resources_in_chan.end())
return 0;
return it->second.size();
@@ -1147,3 +1240,28 @@ void Bridge::set_record_history(const bool val)
this->record_history = val;
}
#endif
+
+void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname)
+{
+#ifdef USE_DATABASE
+ auto options = Database::get_irc_server_options(this->get_bare_jid(),
+ irc_hostname);
+ const auto timeout = std::chrono::seconds(options.lingerTime.value());
+#else
+ const auto timeout = 0s;
+#endif
+
+ const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid();
+ TimedEvent event(std::chrono::steady_clock::now() + timeout, [this, irc_hostname]() {
+ IrcClient* irc = this->find_irc_client(irc_hostname);
+ if (irc)
+ irc->send_quit_command("");
+ }, event_name);
+ TimedEventsManager::instance().add_event(std::move(event));
+}
+
+void Bridge::cancel_linger_timer(const std::string& irc_hostname)
+{
+ const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid();
+ TimedEventsManager::instance().cancel(event_name);
+}
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index 18ebfeb..53d2136 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -38,7 +38,7 @@ using irc_responder_callback_t = std::function<bool(const std::string& irc_hostn
class Bridge
{
public:
- explicit Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller);
+ explicit Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller);
~Bridge() = default;
Bridge(const Bridge&) = delete;
@@ -80,7 +80,7 @@ public:
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, 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_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource);
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);
void set_channel_topic(const Iid& iid, const std::string& subject);
@@ -103,8 +103,8 @@ public:
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);
+ void forward_affiliation_role_change(const Iid& iid, const std::string& from, const std::string& nick,
+ const std::string& affiliation, const std::string& role, const std::string& id);
/**
* Directly send a CTCP PING request to the IRC user
*/
@@ -169,7 +169,7 @@ public:
/**
* Send an unavailable presence from this participant
*/
- void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource="");
+ void send_muc_leave(const Iid& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource = "");
/**
* Send presences to indicate that an user old_nick (ourself if self ==
* true) changed his nick to new_nick. The user_mode is needed because
@@ -231,10 +231,17 @@ public:
*/
void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message);
std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();
+ const std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients() const;
std::set<char> get_chantypes(const std::string& hostname) const;
#ifdef USE_DATABASE
void set_record_history(const bool val);
#endif
+ /**
+ * Start a timer that will send a QUIT command after the
+ * configured linger time is expired.
+ */
+ void quit_or_start_linger_timer(const std::string& irc_hostname);
+ void cancel_linger_timer(const std::string& irc_hostname);
private:
/**
@@ -302,10 +309,11 @@ private:
/**
* Manage which resource is in which channel
*/
- void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource);
- void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource);
- bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const;
- std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const;
+ void add_resource_to_chan(const ChannelKey& channel, const std::string& resource);
+ void remove_resource_from_chan(const ChannelKey& channel, const std::string& resource);
+ bool is_resource_in_chan(const ChannelKey& channel, const std::string& resource) const;
+ void remove_all_resources_from_chan(const ChannelKey& channel);
+ std::size_t number_of_resources_in_chan(const ChannelKey& channel) const;
void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource);
void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource);
diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp
index e2c8a87..dceed74 100644
--- a/src/bridge/colors.hpp
+++ b/src/bridge/colors.hpp
@@ -51,6 +51,6 @@ static const char irc_format_char[] = {
* Returns the body cleaned from any IRC formatting (but without any xhtml),
* and the body as XHTML-IM
*/
-Xmpp::body irc_format_to_xhtmlim(const std::string& str);
+Xmpp::body irc_format_to_xhtmlim(const std::string& s);