Add an ad-hoc command to disconnect a user from one or more IRC server
fix #3077
This commit is contained in:
@@ -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
|
“Gateway shutdown” quit message, except that biboumi does not exit when
|
||||||
using this ad-hoc command.
|
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
|
### Raw IRC messages
|
||||||
|
|
||||||
Biboumi tries to support as many IRC features as possible, but doesn’t
|
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
|
else
|
||||||
return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1")));
|
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;
|
typedef XmlNode Stanza;
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const XmlNode& node);
|
||||||
|
|
||||||
#endif // XMPP_STANZA_INCLUDED
|
#endif // XMPP_STANZA_INCLUDED
|
||||||
|
|||||||
@@ -690,3 +690,8 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe
|
|||||||
++it;
|
++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.
|
* iq_responder_callback_t and remove the callback from the list.
|
||||||
*/
|
*/
|
||||||
void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message);
|
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:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
class IrcMessage
|
class IrcMessage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IrcMessage(std::string&& line);
|
IrcMessage(std::string&& line);
|
||||||
explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector<std::string>&& args);
|
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&& command, std::vector<std::string>&& args);
|
||||||
~IrcMessage();
|
~IrcMessage();
|
||||||
|
|
||||||
std::string prefix;
|
std::string prefix;
|
||||||
|
|||||||
@@ -313,3 +313,169 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com
|
|||||||
session.terminate();
|
session.terminate();
|
||||||
}
|
}
|
||||||
#endif // USE_DATABASE
|
#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 ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node);
|
||||||
void ConfigureIrcServerStep2(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 */
|
#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() = {
|
this->adhoc_commands_handler.get_commands() = {
|
||||||
{"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
|
{"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
|
||||||
{"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", 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)}
|
{"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user