Commit 1f6eea62 authored by louiz’'s avatar louiz’

Add an ad-hoc command to disconnect a user from one or more IRC server

fix #3077
parent 700a6c7b
......@@ -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
......
......@@ -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();
}
......@@ -140,4 +140,6 @@ private:
*/
typedef XmlNode Stanza;
std::ostream& operator<<(std::ostream& os, const XmlNode& node);
#endif // XMPP_STANZA_INCLUDED
......@@ -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;
}
......@@ -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:
/**
......
......@@ -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;
......
......@@ -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));
}
......@@ -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 */
......@@ -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)}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment