From 1f6eea62f46789c0d3ebe7da133ccad2e59c89c8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 21:14:24 +0100 Subject: Add an ad-hoc command to disconnect a user from one or more IRC server fix #3077 --- doc/biboumi.1.md | 6 ++ louloulibs/xmpp/xmpp_stanza.cpp | 5 ++ louloulibs/xmpp/xmpp_stanza.hpp | 2 + src/bridge/bridge.cpp | 5 ++ src/bridge/bridge.hpp | 1 + src/irc/irc_message.hpp | 6 +- src/xmpp/biboumi_adhoc_commands.cpp | 166 ++++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 4 + src/xmpp/biboumi_component.cpp | 3 +- 9 files changed, 194 insertions(+), 4 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 7a33f4f..cf7b893 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -410,6 +410,12 @@ Biboumi supports a few ad-hoc commands, as described in the XEP 0050. “Gateway shutdown” quit message, except that biboumi does not exit when using this ad-hoc command. + - disconnect-from-irc-servers: Disconnect a single user from one or more + IRC server. The user is immediately disconnected by closing the socket, + no message is sent to the IRC server, but the user is of course notified + with an XMPP message. The administrator can disconnect any user, while + the other users can only disconnect themselves. + ### Raw IRC messages Biboumi tries to support as many IRC features as possible, but doesn’t diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index 4c0f5c7..d1c2e0f 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -260,3 +260,8 @@ std::string sanitize(const std::string& data) else return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); } + +std::ostream& operator<<(std::ostream& os, const XmlNode& node) +{ + return os << node.to_string(); +} diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 9cf7d8d..b1ba54a 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -140,4 +140,6 @@ private: */ typedef XmlNode Stanza; +std::ostream& operator<<(std::ostream& os, const XmlNode& node); + #endif // XMPP_STANZA_INCLUDED diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 5badb18..a1ba71a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -690,3 +690,8 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe ++it; } } + +std::unordered_map>& Bridge::get_irc_clients() +{ + return this->irc_clients; +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f1ecf25..7b8df8f 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -187,6 +187,7 @@ public: * iq_responder_callback_t and remove the callback from the list. */ void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); + std::unordered_map>& get_irc_clients(); private: /** diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp index a0bd772..01e78cf 100644 --- a/src/irc/irc_message.hpp +++ b/src/irc/irc_message.hpp @@ -8,9 +8,9 @@ class IrcMessage { public: - explicit IrcMessage(std::string&& line); - explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); - explicit IrcMessage(std::string&& command, std::vector&& args); + IrcMessage(std::string&& line); + IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); + IrcMessage(std::string&& command, std::vector&& args); ~IrcMessage(); std::string prefix; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index ff0c8d4..84df2b3 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -313,3 +313,169 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com session.terminate(); } #endif // USE_DATABASE + +void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + const Jid owner(session.get_owner_jid()); + if (owner.bare() != Config::get("admin", "")) + { // A non-admin is not allowed to disconnect other users, only + // him/herself, so we just skip this step + auto next_step = session.get_next_step(); + next_step(xmpp_component, session, command_node); + } + else + { // Send a form to select the user to disconnect + auto biboumi_component = static_cast(xmpp_component); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from selected IRC servers"); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose a user JID"); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "jid"; + jids_field["type"] = "list-single"; + jids_field["label"] = "The JID to disconnect"; + XmlNode required("required"); + jids_field.add_child(std::move(required)); + for (Bridge* bridge: biboumi_component->get_bridges()) + { + XmlNode option("option"); + option["label"] = bridge->get_jid(); + XmlNode value("value"); + value.set_inner(bridge->get_jid()); + option.add_child(std::move(value)); + jids_field.add_child(std::move(option)); + } + x.add_child(std::move(jids_field)); + command_node.add_child(std::move(x)); + } +} + +void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + // If no JID is contained in the command node, it means we skipped the + // previous stage, and the jid to disconnect is the executor's jid + std::string jid_to_disconnect = session.get_owner_jid(); + + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) + { + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "jid") + { + if (const XmlNode* value = field->get_child("value", "jabber:x:data")) + jid_to_disconnect = value->get_inner(); + } + } + + // Save that JID for the last step + session.vars["jid"] = jid_to_disconnect; + + // Send a data form to let the user choose which server to disconnect the + // user from + command_node.delete_all_children(); + auto biboumi_component = static_cast(xmpp_component); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from selected IRC servers"); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose one or more servers to disconnect this JID from"); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "irc-servers"; + jids_field["type"] = "list-multi"; + jids_field["label"] = "The servers to disconnect from"; + XmlNode required("required"); + jids_field.add_child(std::move(required)); + Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + + if (!bridge || bridge->get_irc_clients().empty()) + { + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("User "s + jid_to_disconnect + " is not connected to any IRC server."); + command_node.add_child(std::move(note)); + session.terminate(); + return ; + } + + for (const auto& pair: bridge->get_irc_clients()) + { + XmlNode option("option"); + option["label"] = pair.first; + XmlNode value("value"); + value.set_inner(pair.first); + option.add_child(std::move(value)); + jids_field.add_child(std::move(option)); + } + x.add_child(std::move(jids_field)); + + XmlNode message_field("field"); + message_field["var"] = "quit-message"; + message_field["type"] = "text-single"; + message_field["label"] = "Quit message"; + XmlNode message_value("value"); + message_value.set_inner("Killed by admin"); + message_field.add_child(std::move(message_value)); + x.add_child(std::move(message_field)); + + command_node.add_child(std::move(x)); +} + +void DisconnectUserFromServerStep3(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + const auto it = session.vars.find("jid"); + if (it == session.vars.end()) + return ; + const auto jid_to_disconnect = it->second; + + std::vector servers; + std::string quit_message; + + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) + { + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + if (field->get_tag("var") == "irc-servers") + { + for (const XmlNode* value: field->get_children("value", "jabber:x:data")) + servers.push_back(value->get_inner()); + } + else if (field->get_tag("var") == "quit-message") + if (const XmlNode* value = field->get_child("value", "jabber:x:data")) + quit_message = value->get_inner(); + } + } + + auto biboumi_component = static_cast(xmpp_component); + Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + auto& clients = bridge->get_irc_clients(); + + std::size_t number = 0; + + for (const auto& hostname: servers) + { + auto it = clients.find(hostname); + if (it != clients.end()) + { + it->second->on_error({"ERROR", {quit_message}}); + clients.erase(it); + number++; + } + } + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + std::string msg = jid_to_disconnect + " was disconnected from " + std::to_string(number) + " IRC server"; + if (number > 1) + msg += "s"; + msg += "."; + note.set_inner(msg); + command_node.add_child(std::move(note)); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index e530fa7..9377d13 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -13,4 +13,8 @@ void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep3(XmppComponent*, AdhocSession& session, XmlNode& command_node); + #endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6f1e585..72b767f 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -55,7 +55,8 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st 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 a user from the gateway", true)}, + {"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)} }; -- cgit v1.2.3