M doc/biboumi.1.md => doc/biboumi.1.md +6 -0
@@ 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
M louloulibs/xmpp/xmpp_stanza.cpp => louloulibs/xmpp/xmpp_stanza.cpp +5 -0
@@ 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();
+}
M louloulibs/xmpp/xmpp_stanza.hpp => louloulibs/xmpp/xmpp_stanza.hpp +2 -0
@@ 140,4 140,6 @@ private:
*/
typedef XmlNode Stanza;
+std::ostream& operator<<(std::ostream& os, const XmlNode& node);
+
#endif // XMPP_STANZA_INCLUDED
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +5 -0
@@ 690,3 690,8 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe
++it;
}
}
+
+std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients()
+{
+ return this->irc_clients;
+}
M src/bridge/bridge.hpp => src/bridge/bridge.hpp +1 -0
@@ 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<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();
private:
/**
M src/irc/irc_message.hpp => src/irc/irc_message.hpp +3 -3
@@ 8,9 8,9 @@
class IrcMessage
{
public:
- explicit IrcMessage(std::string&& line);
- explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector<std::string>&& args);
- explicit IrcMessage(std::string&& command, std::vector<std::string>&& args);
+ IrcMessage(std::string&& line);
+ IrcMessage(std::string&& prefix, std::string&& command, std::vector<std::string>&& args);
+ IrcMessage(std::string&& command, std::vector<std::string>&& args);
~IrcMessage();
std::string prefix;
M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +166 -0
@@ 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));
+}
M src/xmpp/biboumi_adhoc_commands.hpp => src/xmpp/biboumi_adhoc_commands.hpp +4 -0
@@ 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 */
M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +2 -1
@@ 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)}
};