diff options
Diffstat (limited to 'src/bridge/bridge.cpp')
-rw-r--r-- | src/bridge/bridge.cpp | 218 |
1 files changed, 163 insertions, 55 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a0ecc6e..81ca147 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> @@ -22,20 +23,24 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) #ifdef USE_DATABASE const auto jid = bridge.get_bare_jid(); auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); - return options.encodingIn.value(); + auto result = options.col<Database::EncodingIn>(); + if (!result.empty()) + return result; #else - return {"ISO-8859-1"}; + (void)bridge; + (void)iid; #endif + return {"ISO-8859-1"}; } -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) { #ifdef USE_DATABASE const auto options = Database::get_global_options(this->user_jid); - this->set_record_history(options.recordHistory.value()); + this->set_record_history(options.col<Database::RecordHistory>()); #endif } @@ -58,10 +63,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 +172,7 @@ 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(); 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); @@ -251,21 +256,24 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) else irc->send_channel_message(iid.get_local(), line); + std::string uuid; #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); if (this->record_history) - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), 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); + this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), + this->user_jid + "/" + resource, uuid); } } -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 +281,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 +337,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 +426,60 @@ 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()); + 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 goptions = Database::get_global_options(this->user_jid); + if (goptions.col<Database::Persistent>()) + persistent = true; + else + { + const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, iid.get_server(), iid.get_local()); + persistent = coptions.col<Database::Persistent>(); + } +#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 if (channel->joined) + { + this->send_muc_leave(iid, channel->get_self()->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 && channel->joined) + this->send_muc_leave(iid, channel->get_self()->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); } @@ -402,7 +487,7 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, ResultSetInfo rs_info) { - auto& list = channel_list_cache[iid.get_server()]; + auto& list = this->channel_list_cache[iid.get_server()]; // 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 @@ -425,7 +510,7 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq if (irc_hostname != iid.get_server()) return false; - auto& list = channel_list_cache[iid.get_server()]; + auto& list = this->channel_list_cache[iid.get_server()]; if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER") @@ -483,7 +568,6 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq { 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") @@ -503,7 +587,7 @@ bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const R 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(); + auto end = channel_list.channels.end(); if (channel_list.complete) { begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) @@ -605,9 +689,12 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: this->add_waiting_irc(std::move(cb)); } -void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) +void Bridge::set_channel_topic(const Iid& iid, std::string subject) { IrcClient* irc = this->get_irc_client(iid.get_server()); + std::string::size_type pos{0}; + while ((pos = subject.find('\n', pos)) != std::string::npos) + subject[pos] = ' '; irc->send_topic_command(iid.get_local(), subject); } @@ -665,9 +752,10 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) { + Jid from(to_jid); IrcClient* irc = this->get_irc_client(iid.get_server()); IrcChannel* chan = irc->get_channel(iid.get_local()); - if (!chan->joined) + if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource)) { this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed", "", true); @@ -756,13 +844,13 @@ 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() && this->record_history) - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), 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, - this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, {}); } } @@ -793,18 +881,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, const 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) + if (self && irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); } @@ -903,18 +997,22 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c 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) +void Bridge::send_room_history(const std::string& hostname, 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()); + const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.col<Database::MaxHistoryLength>()); + chan_name.append(utils::empty_if_fixed_server("%" + hostname)); 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(), + const auto seconds = line.col<Database::Date>(); + this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(), this->user_jid + "/" + resource, seconds); } +#else + (void)hostname; + (void)chan_name; + (void)resource; #endif } @@ -960,7 +1058,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->xmpp.send_iq_version_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname), this->user_jid + "/" + *resources.begin()); } @@ -968,12 +1066,12 @@ 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]; if (resources.begin() != resources.end()) - this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), + this->xmpp.send_ping_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } @@ -1033,6 +1131,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 +1173,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 +1207,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(); |