diff options
-rw-r--r-- | src/bridge/bridge.cpp | 128 | ||||
-rw-r--r-- | src/bridge/bridge.hpp | 37 | ||||
-rw-r--r-- | src/irc/irc_channel.hpp | 2 | ||||
-rw-r--r-- | src/irc/irc_client.cpp | 2 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.cpp | 19 |
5 files changed, 142 insertions, 46 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 484c860..bfd5d68 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -156,9 +156,15 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) } } -bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password) -{ - IrcClient* irc = this->make_irc_client(iid.get_server(), nickname); +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(); + 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); + if (!res_in_chan) + this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource); if (iid.get_local().empty()) { // Join the dummy channel if (irc->is_welcomed()) @@ -185,6 +191,8 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const { irc->send_join_command(iid.get_local(), password); return true; + } else if (!res_in_chan) { + this->generate_channel_join_for_resource(iid, resource); } return false; } @@ -193,11 +201,12 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) { if (iid.get_server().empty()) { - this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "", - "cancel", "remote-server-not-found", - std::to_string(iid) + " is not a valid channel name. " - "A correct room jid is of the form: #<chan>%<server>", - false); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_stanza_error("message", this->user_jid + "/" + resource, std::to_string(iid), "", + "cancel", "remote-server-not-found", + std::to_string(iid) + " is not a valid channel name. " + "A correct room jid is of the form: #<chan>%<server>", + false); return; } IrcClient* irc = this->get_irc_client(iid.get_server()); @@ -226,8 +235,9 @@ 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); - this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), - this->make_xmpp_body(line), this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), + this->make_xmpp_body(line), this->user_jid + "/" + resource); } } @@ -554,8 +564,13 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { const auto encoding = in_encoding_for(*this, iid); if (muc) - this->xmpp.send_muc_message(std::to_string(iid), nick, - this->make_xmpp_body(body, encoding), this->user_jid); + { + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + { + this->xmpp.send_muc_message(std::to_string(iid), nick, + this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + } + } else { std::string target = std::to_string(iid); @@ -566,8 +581,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st target = it->second; fulljid = true; } - this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), - this->user_jid, "chat", fulljid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + { + this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, "chat", fulljid); + } } } @@ -580,7 +598,8 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -596,8 +615,9 @@ void Bridge::send_nick_change(Iid&& iid, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp.send_nick_change(std::to_string(iid), - old_nick, new_nick, affiliation, role, this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_nick_change(std::to_string(iid), + old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self); } void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) @@ -614,7 +634,10 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho body = msg; const auto encoding = in_encoding_for(*this, {from}); - this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid, "chat"); + 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"); + } } void Bridge::send_user_join(const std::string& hostname, @@ -627,15 +650,21 @@ void Bridge::send_user_join(const std::string& hostname, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, - affiliation, role, this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + { + this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, + 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) { const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( - "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid, who); + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + { + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( + "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); + } } std::string Bridge::get_own_nick(const Iid& iid) @@ -653,12 +682,14 @@ size_t Bridge::active_clients() const void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { - this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + 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) @@ -667,7 +698,8 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::string affiliation; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); - this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + 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) @@ -722,7 +754,7 @@ std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_cli return this->irc_clients; } -void Bridge::add_resource_to_chan(const std::string& channel, const std::string& resource) +void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource) { auto it = this->resources_in_chan.find(channel); if (it == this->resources_in_chan.end()) @@ -731,7 +763,7 @@ void Bridge::add_resource_to_chan(const std::string& channel, const std::string& it->second.insert(resource); } -void Bridge::remove_resource_from_chan(const std::string& channel, const std::string& resource) +void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource) { auto it = this->resources_in_chan.find(channel); if (it != this->resources_in_chan.end()) @@ -742,7 +774,7 @@ void Bridge::remove_resource_from_chan(const std::string& channel, const std::st } } -bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& resource) const +bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const { auto it = this->resources_in_chan.find(channel); if (it != this->resources_in_chan.end()) @@ -750,3 +782,43 @@ bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& return true; return false; } + +void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it == this->resources_in_server.end()) + this->resources_in_server[irc_hostname] = {resource}; + else + it->second.insert(resource); +} + +void Bridge::remove_resource_from_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it != this->resources_in_server.end()) + { + it->second.erase(resource); + if (it->second.empty()) + this->resources_in_server.erase(it); + } +} + +bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) const +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it != this->resources_in_server.end()) + if (it->second.count(resource) == 1) + return true; + return false; +} + +void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + IrcChannel* channel = irc->get_channel(iid.get_local()); + // Send the occupant list + for (const auto& user: channel->get_users()) + { + + } +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 469a959..e614779 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -65,7 +65,7 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password); + bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource); 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"); @@ -195,11 +195,9 @@ public: std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients(); /** - * Manage which resource is in which channel + * Manage which resource is connected to which IRC server */ - void add_resource_to_chan(const std::string& channel, const std::string& resource); - void remove_resource_from_chan(const std::string& channel, const std::string& resource); - bool is_resource_in_chan(const std::string& channel, const std::string& resource) const; + private: /** @@ -218,7 +216,7 @@ private: */ IrcClient* find_irc_client(const std::string& hostname); /** - * The JID of the user associated with this bridge. Messages from/to this + * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. */ const std::string user_jid; @@ -251,10 +249,33 @@ private: * response iq. */ std::vector<irc_responder_callback_t> waiting_irc; + + /** + * Resources to IRC channel/server mapping: + */ + using Resource = std::string; + using ChannelName = std::string; + using IrcHostname = std::string; + using ChannelKey = std::tuple<ChannelName, IrcHostname>; + std::map<ChannelKey, std::set<Resource>> resources_in_chan; + std::map<IrcHostname, std::set<Resource>> resources_in_server; + /** + * 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; + + 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); + bool is_resource_in_server(const IrcHostname& irc_hostname, const std::string& resource) const; + /** - * Keep track of which resource is in which channel. + * Generate all the stanzas to be sent to this resource, simulating a join on this channel. + * This means sending the whole user list, the topic, etc + * TODO: send message history */ - std::map<std::string, std::set<std::string>> resources_in_chan; + void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); }; struct IRCNotConnected: public std::exception diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index e22947b..3319505 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -31,6 +31,8 @@ public: IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); void remove_all_users(); + const std::vector<std::unique_ptr<IrcUser>>& get_users() const + { return this->users; } protected: std::unique_ptr<IrcUser> self; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0b1b079..ae68528 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -701,7 +701,7 @@ void IrcClient::empty_motd(const IrcMessage&) void IrcClient::on_empty_topic(const IrcMessage& message) { - const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 1]); + const std::string chan_name = utils::tolower(message.arguments[1]); log_debug("empty topic for " << chan_name); IrcChannel* channel = this->get_channel(chan_name); if (channel) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e5aee9a..6a9bc87 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -105,26 +105,27 @@ void BiboumiComponent::clean() void BiboumiComponent::handle_presence(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"); // Check for mandatory tags - if (from.empty()) + if (from_str.empty()) { log_warning("Received an invalid presence stanza: tag 'from' is missing."); return; } if (to_str.empty()) { - this->send_stanza_error("presence", from, this->served_hostname, id, + this->send_stanza_error("presence", from_str, this->served_hostname, id, "modify", "bad-request", "Missing 'to' tag"); return; } - Bridge* bridge = this->get_user_bridge(from); + Bridge* bridge = this->get_user_bridge(from_str); Jid to(to_str); + Jid from(from_str); Iid iid(to.local); // An error stanza is sent whenever we exit this function without @@ -136,7 +137,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("presence", from, to_str, id, + this->send_stanza_error("presence", from_str, to_str, id, error_type, error_name, ""); }); @@ -151,8 +152,8 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) bridge->send_irc_nick_change(iid, to.resource); const XmlNode* x = stanza.get_child("x", MUC_NS); const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; - bridge->join_irc_channel(iid, to.resource, - password ? password->get_inner() : ""); + bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "", + from.resource); } else if (type == "unavailable") { @@ -164,12 +165,12 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) { // An user wants to join an invalid IRC channel, return a presence error to him if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, from); + this->send_invalid_room_error(to.local, to.resource, from_str); } } catch (const IRCNotConnected& ex) { - this->send_stanza_error("presence", from, to_str, id, + this->send_stanza_error("presence", from_str, to_str, id, "cancel", "remote-server-not-found", "Not connected to IRC server "s + ex.hostname, true); |