From 1f6eea62f46789c0d3ebe7da133ccad2e59c89c8 Mon Sep 17 00:00:00 2001
From: Florent Le Coz <louiz@louiz.org>
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
---
 src/xmpp/biboumi_adhoc_commands.cpp | 166 ++++++++++++++++++++++++++++++++++++
 src/xmpp/biboumi_adhoc_commands.hpp |   4 +
 src/xmpp/biboumi_component.cpp      |   3 +-
 3 files changed, 172 insertions(+), 1 deletion(-)

(limited to 'src/xmpp')

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<BiboumiComponent*>(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<BiboumiComponent*>(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<std::string> 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<BiboumiComponent*>(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> 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