summaryrefslogtreecommitdiff
path: root/src/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmpp')
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp635
-rw-r--r--src/xmpp/biboumi_adhoc_commands.hpp23
-rw-r--r--src/xmpp/biboumi_component.cpp632
-rw-r--r--src/xmpp/biboumi_component.hpp109
4 files changed, 1399 insertions, 0 deletions
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
new file mode 100644
index 0000000..eec930d
--- /dev/null
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -0,0 +1,635 @@
+#include <xmpp/biboumi_adhoc_commands.hpp>
+#include <xmpp/biboumi_component.hpp>
+#include <config/config.hpp>
+#include <utils/string.hpp>
+#include <utils/split.hpp>
+#include <xmpp/jid.hpp>
+
+#include <biboumi.h>
+
+#ifdef USE_DATABASE
+#include <database/database.hpp>
+#endif
+
+#include <louloulibs.h>
+
+#include <algorithm>
+
+using namespace std::string_literals;
+
+void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node)
+{
+ 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 the gateway");
+ x.add_child(std::move(title));
+ XmlNode instructions("instructions");
+ instructions.set_inner("Choose a user JID and a quit message");
+ x.add_child(std::move(instructions));
+ XmlNode jids_field("field");
+ jids_field["var"] = "jids";
+ jids_field["type"] = "list-multi";
+ jids_field["label"] = "The JIDs 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));
+
+ 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("Disconnected 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 DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)
+{
+ auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+
+ // Find out if the jids, and the quit message are provided in the form.
+ std::string quit_message;
+ const XmlNode* x = command_node.get_child("x", "jabber:x:data");
+ if (x)
+ {
+ const XmlNode* message_field = nullptr;
+ const XmlNode* jids_field = nullptr;
+ for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
+ if (field->get_tag("var") == "jids")
+ jids_field = field;
+ else if (field->get_tag("var") == "quit-message")
+ message_field = field;
+ if (message_field)
+ {
+ const XmlNode* value = message_field->get_child("value", "jabber:x:data");
+ if (value)
+ quit_message = value->get_inner();
+ }
+ if (jids_field)
+ {
+ std::size_t num = 0;
+ for (const XmlNode* value: jids_field->get_children("value", "jabber:x:data"))
+ {
+ Bridge* bridge = biboumi_component.find_user_bridge(value->get_inner());
+ if (bridge)
+ {
+ bridge->shutdown(quit_message);
+ num++;
+ }
+ }
+ command_node.delete_all_children();
+
+ XmlNode note("note");
+ note["type"] = "info";
+ if (num == 0)
+ note.set_inner("No user were disconnected.");
+ else if (num == 1)
+ note.set_inner("1 user has been disconnected.");
+ else
+ note.set_inner(std::to_string(num) + " users have been disconnected.");
+ command_node.add_child(std::move(note));
+ return;
+ }
+ }
+ XmlNode error(ADHOC_NS":error");
+ error["type"] = "modify";
+ XmlNode condition(STANZA_NS":bad-request");
+ error.add_child(std::move(condition));
+ command_node.add_child(std::move(error));
+ session.terminate();
+}
+
+#ifdef USE_DATABASE
+void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+{
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+ std::string server_domain;
+ if ((server_domain = Config::get("fixed_irc_server", "")).empty())
+ server_domain = target.local;
+ auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain,
+ server_domain);
+
+ XmlNode x("jabber:x:data:x");
+ x["type"] = "form";
+ XmlNode title("title");
+ title.set_inner("Configure the IRC server "s + server_domain);
+ x.add_child(std::move(title));
+ XmlNode instructions("instructions");
+ instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain);
+ x.add_child(std::move(instructions));
+
+ XmlNode required("required");
+
+ XmlNode ports("field");
+ ports["var"] = "ports";
+ ports["type"] = "text-multi";
+ ports["label"] = "Ports";
+ ports["desc"] = "List of ports to try, without TLS. Defaults: 6667.";
+ auto vals = utils::split(options.ports.value(), ';', false);
+ for (const auto& val: vals)
+ {
+ XmlNode ports_value("value");
+ ports_value.set_inner(val);
+ ports.add_child(std::move(ports_value));
+ }
+ ports.add_child(required);
+ x.add_child(std::move(ports));
+
+#ifdef BOTAN_FOUND
+ XmlNode tls_ports("field");
+ tls_ports["var"] = "tls_ports";
+ tls_ports["type"] = "text-multi";
+ tls_ports["label"] = "TLS ports";
+ tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670.";
+ vals = utils::split(options.tlsPorts.value(), ';', false);
+ for (const auto& val: vals)
+ {
+ XmlNode tls_ports_value("value");
+ tls_ports_value.set_inner(val);
+ tls_ports.add_child(std::move(tls_ports_value));
+ }
+ tls_ports.add_child(required);
+ x.add_child(std::move(tls_ports));
+
+ XmlNode verify_cert("field");
+ verify_cert["var"] = "verify_cert";
+ verify_cert["type"] = "boolean";
+ verify_cert["label"] = "Verify certificate";
+ verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid";
+ XmlNode verify_cert_value("value");
+ if (options.verifyCert.value())
+ verify_cert_value.set_inner("true");
+ else
+ verify_cert_value.set_inner("false");
+ verify_cert.add_child(std::move(verify_cert_value));
+ x.add_child(std::move(verify_cert));
+
+ XmlNode fingerprint("field");
+ fingerprint["var"] = "fingerprint";
+ fingerprint["type"] = "text-single";
+ fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust.";
+ if (!options.trustedFingerprint.value().empty())
+ {
+ XmlNode fingerprint_value("value");
+ fingerprint_value.set_inner(options.trustedFingerprint.value());
+ fingerprint.add_child(std::move(fingerprint_value));
+ }
+ fingerprint.add_child(required);
+ x.add_child(std::move(fingerprint));
+#endif
+
+ XmlNode pass("field");
+ pass["var"] = "pass";
+ pass["type"] = "text-private";
+ pass["label"] = "Server password (to be used in a PASS command when connecting)";
+ if (!options.pass.value().empty())
+ {
+ XmlNode pass_value("value");
+ pass_value.set_inner(options.pass.value());
+ pass.add_child(std::move(pass_value));
+ }
+ pass.add_child(required);
+ x.add_child(std::move(pass));
+
+ XmlNode after_cnt_cmd("field");
+ after_cnt_cmd["var"] = "after_connect_command";
+ after_cnt_cmd["type"] = "text-single";
+ after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server.";
+ after_cnt_cmd["label"] = "After-connection IRC command";
+ if (!options.afterConnectionCommand.value().empty())
+ {
+ XmlNode after_cnt_cmd_value("value");
+ after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value());
+ after_cnt_cmd.add_child(std::move(after_cnt_cmd_value));
+ }
+ after_cnt_cmd.add_child(required);
+ x.add_child(std::move(after_cnt_cmd));
+
+ if (Config::get("realname_customization", "true") == "true")
+ {
+ XmlNode username("field");
+ username["var"] = "username";
+ username["type"] = "text-single";
+ username["label"] = "Username";
+ if (!options.username.value().empty())
+ {
+ XmlNode username_value("value");
+ username_value.set_inner(options.username.value());
+ username.add_child(std::move(username_value));
+ }
+ username.add_child(required);
+ x.add_child(std::move(username));
+
+ XmlNode realname("field");
+ realname["var"] = "realname";
+ realname["type"] = "text-single";
+ realname["label"] = "Realname";
+ if (!options.realname.value().empty())
+ {
+ XmlNode realname_value("value");
+ realname_value.set_inner(options.realname.value());
+ realname.add_child(std::move(realname_value));
+ }
+ realname.add_child(required);
+ x.add_child(std::move(realname));
+ }
+
+ XmlNode encoding_out("field");
+ encoding_out["var"] = "encoding_out";
+ encoding_out["type"] = "text-single";
+ encoding_out["desc"] = "The encoding used when sending messages to the IRC server.";
+ encoding_out["label"] = "Out encoding";
+ if (!options.encodingOut.value().empty())
+ {
+ XmlNode encoding_out_value("value");
+ encoding_out_value.set_inner(options.encodingOut.value());
+ encoding_out.add_child(std::move(encoding_out_value));
+ }
+ encoding_out.add_child(required);
+ x.add_child(std::move(encoding_out));
+
+ XmlNode encoding_in("field");
+ encoding_in["var"] = "encoding_in";
+ encoding_in["type"] = "text-single";
+ encoding_in["desc"] = "The encoding used to decode message received from the IRC server.";
+ encoding_in["label"] = "In encoding";
+ if (!options.encodingIn.value().empty())
+ {
+ XmlNode encoding_in_value("value");
+ encoding_in_value.set_inner(options.encodingIn.value());
+ encoding_in.add_child(std::move(encoding_in_value));
+ }
+ encoding_in.add_child(required);
+ x.add_child(std::move(encoding_in));
+
+
+ command_node.add_child(std::move(x));
+}
+
+void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+{
+ const XmlNode* x = command_node.get_child("x", "jabber:x:data");
+ if (x)
+ {
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+ std::string server_domain;
+ if ((server_domain = Config::get("fixed_irc_server", "")).empty())
+ server_domain = target.local;
+ auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain,
+ server_domain);
+ for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
+ {
+ const XmlNode* value = field->get_child("value", "jabber:x:data");
+ const std::vector<const XmlNode*> values = field->get_children("value", "jabber:x:data");
+ if (field->get_tag("var") == "ports")
+ {
+ std::string ports;
+ for (const auto& val: values)
+ ports += val->get_inner() + ";";
+ options.ports = ports;
+ }
+
+#ifdef BOTAN_FOUND
+ else if (field->get_tag("var") == "tls_ports")
+ {
+ std::string ports;
+ for (const auto& val: values)
+ ports += val->get_inner() + ";";
+ options.tlsPorts = ports;
+ }
+
+ else if (field->get_tag("var") == "verify_cert" && value
+ && !value->get_inner().empty())
+ {
+ auto val = to_bool(value->get_inner());
+ options.verifyCert = val;
+ }
+
+ else if (field->get_tag("var") == "fingerprint" && value &&
+ !value->get_inner().empty())
+ {
+ options.trustedFingerprint = value->get_inner();
+ }
+
+#endif // BOTAN_FOUND
+
+ else if (field->get_tag("var") == "pass" &&
+ value && !value->get_inner().empty())
+ options.pass = value->get_inner();
+
+ else if (field->get_tag("var") == "after_connect_command" &&
+ value && !value->get_inner().empty())
+ options.afterConnectionCommand = value->get_inner();
+
+ else if (field->get_tag("var") == "username" &&
+ value && !value->get_inner().empty())
+ {
+ auto username = value->get_inner();
+ // The username must not contain spaces
+ std::replace(username.begin(), username.end(), ' ', '_');
+ options.username = username;
+ }
+
+ else if (field->get_tag("var") == "realname" &&
+ value && !value->get_inner().empty())
+ options.realname = value->get_inner();
+
+ else if (field->get_tag("var") == "encoding_out" &&
+ value && !value->get_inner().empty())
+ options.encodingOut = value->get_inner();
+
+ else if (field->get_tag("var") == "encoding_in" &&
+ value && !value->get_inner().empty())
+ options.encodingIn = value->get_inner();
+
+ }
+
+ options.update();
+
+ command_node.delete_all_children();
+ XmlNode note("note");
+ note["type"] = "info";
+ note.set_inner("Configuration successfully applied.");
+ command_node.add_child(std::move(note));
+ return;
+ }
+ XmlNode error(ADHOC_NS":error");
+ error["type"] = "modify";
+ XmlNode condition(STANZA_NS":bad-request");
+ error.add_child(std::move(condition));
+ command_node.add_child(std::move(error));
+ session.terminate();
+}
+
+void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+{
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+ const Iid iid(target.local);
+ auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain,
+ iid.get_server(), iid.get_local());
+
+ XmlNode x("jabber:x:data:x");
+ x["type"] = "form";
+ XmlNode title("title");
+ title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server());
+ x.add_child(std::move(title));
+ XmlNode instructions("instructions");
+ instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local());
+ x.add_child(std::move(instructions));
+
+ XmlNode required("required");
+
+ XmlNode encoding_out("field");
+ encoding_out["var"] = "encoding_out";
+ encoding_out["type"] = "text-single";
+ encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel";
+ encoding_out["label"] = "Out encoding";
+ if (!options.encodingOut.value().empty())
+ {
+ XmlNode encoding_out_value("value");
+ encoding_out_value.set_inner(options.encodingOut.value());
+ encoding_out.add_child(std::move(encoding_out_value));
+ }
+ encoding_out.add_child(required);
+ x.add_child(std::move(encoding_out));
+
+ XmlNode encoding_in("field");
+ encoding_in["var"] = "encoding_in";
+ encoding_in["type"] = "text-single";
+ encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel";
+ encoding_in["label"] = "In encoding";
+ if (!options.encodingIn.value().empty())
+ {
+ XmlNode encoding_in_value("value");
+ encoding_in_value.set_inner(options.encodingIn.value());
+ encoding_in.add_child(std::move(encoding_in_value));
+ }
+ encoding_in.add_child(required);
+ x.add_child(std::move(encoding_in));
+
+ command_node.add_child(std::move(x));
+}
+
+void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+{
+ const XmlNode* x = command_node.get_child("x", "jabber:x:data");
+ if (x)
+ {
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+ const Iid iid(target.local);
+ auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain,
+ iid.get_server(), iid.get_local());
+ for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
+ {
+ const XmlNode* value = field->get_child("value", "jabber:x:data");
+
+ if (field->get_tag("var") == "encoding_out" &&
+ value && !value->get_inner().empty())
+ options.encodingOut = value->get_inner();
+
+ else if (field->get_tag("var") == "encoding_in" &&
+ value && !value->get_inner().empty())
+ options.encodingIn = value->get_inner();
+ }
+
+ options.update();
+
+ command_node.delete_all_children();
+ XmlNode note("note");
+ note["type"] = "info";
+ note.set_inner("Configuration successfully applied.");
+ command_node.add_child(std::move(note));
+ return;
+ }
+ XmlNode error(ADHOC_NS":error");
+ error["type"] = "modify";
+ XmlNode condition(STANZA_NS":bad-request");
+ error.add_child(std::move(condition));
+ command_node.add_child(std::move(error));
+ 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
new file mode 100644
index 0000000..2763a9f
--- /dev/null
+++ b/src/xmpp/biboumi_adhoc_commands.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+
+#include <xmpp/adhoc_command.hpp>
+#include <xmpp/adhoc_session.hpp>
+#include <xmpp/xmpp_stanza.hpp>
+
+class XmppComponent;
+
+void DisconnectUserStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+
+void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+
+void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void ConfigureIrcChannelStep2(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);
+
+
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
new file mode 100644
index 0000000..a6aac21
--- /dev/null
+++ b/src/xmpp/biboumi_component.cpp
@@ -0,0 +1,632 @@
+#include <xmpp/biboumi_component.hpp>
+
+#include <utils/timed_events.hpp>
+#include <utils/scopeguard.hpp>
+#include <utils/tolower.hpp>
+#include <logger/logger.hpp>
+#include <xmpp/adhoc_command.hpp>
+#include <xmpp/biboumi_adhoc_commands.hpp>
+#include <bridge/list_element.hpp>
+#include <config/config.hpp>
+#include <xmpp/jid.hpp>
+#include <utils/sha1.hpp>
+
+#include <stdexcept>
+#include <iostream>
+
+#include <stdio.h>
+
+#include <louloulibs.h>
+#include <biboumi.h>
+
+#include <uuid.h>
+
+#ifdef SYSTEMD_FOUND
+# include <systemd/sd-daemon.h>
+#endif
+
+using namespace std::string_literals;
+
+static std::set<std::string> kickable_errors{
+ "gone",
+ "internal-server-error",
+ "item-not-found",
+ "jid-malformed",
+ "recipient-unavailable",
+ "redirect",
+ "remote-server-not-found",
+ "remote-server-timeout",
+ "service-unavailable",
+ "malformed-error"
+ };
+
+
+BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
+ XmppComponent(poller, hostname, secret),
+ irc_server_adhoc_commands_handler(*this),
+ irc_channel_adhoc_commands_handler(*this)
+{
+ this->stanza_handlers.emplace("presence",
+ std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1));
+ this->stanza_handlers.emplace("message",
+ std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1));
+ this->stanza_handlers.emplace("iq",
+ std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1));
+
+ 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 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)}
+ };
+
+#ifdef USE_DATABASE
+ AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false);
+ if (!Config::get("fixed_irc_server", "").empty())
+ {
+ this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure",
+ configure_server_command));
+ }
+#endif
+
+ this->irc_server_adhoc_commands_handler.get_commands() = {
+#ifdef USE_DATABASE
+ {"configure", configure_server_command},
+#endif
+ };
+ this->irc_channel_adhoc_commands_handler.get_commands() = {
+#ifdef USE_DATABASE
+ {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)},
+#endif
+ };
+}
+
+void BiboumiComponent::shutdown()
+{
+ for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
+ {
+ it->second->shutdown("Gateway shutdown");
+ }
+}
+
+void BiboumiComponent::clean()
+{
+ auto it = this->bridges.begin();
+ while (it != this->bridges.end())
+ {
+ it->second->clean();
+ if (it->second->active_clients() == 0)
+ it = this->bridges.erase(it);
+ else
+ ++it;
+ }
+}
+
+void BiboumiComponent::handle_presence(const Stanza& stanza)
+{
+ std::string from_str = stanza.get_tag("from");
+ std::string id = stanza.get_tag("id");
+ std::string to_str = stanza.get_tag("to");
+ std::string type = stanza.get_tag("type");
+
+ // Check for mandatory tags
+ if (from_str.empty())
+ {
+ log_warning("Received an invalid presence stanza: tag 'from' is missing.");
+ return;
+ }
+ if (to_str.empty())
+ {
+ this->send_stanza_error("presence", from_str, this->served_hostname, id,
+ "modify", "bad-request", "Missing 'to' tag");
+ return;
+ }
+
+ Bridge* bridge = this->get_user_bridge(from_str);
+ Jid to(to_str);
+ Jid from(from_str);
+ Iid iid(to.local);
+
+ // An error stanza is sent whenever we exit this function without
+ // disabling this scopeguard. If error_type and error_name are not
+ // changed, the error signaled is internal-server-error. Change their
+ // value to signal an other kind of error. For example
+ // feature-not-implemented, etc. Any non-error process should reach the
+ // stanza_error.disable() call at the end of the function.
+ std::string error_type("cancel");
+ std::string error_name("internal-server-error");
+ utils::ScopeGuard stanza_error([&](){
+ this->send_stanza_error("presence", from_str, to_str, id,
+ error_type, error_name, "");
+ });
+
+ try {
+ if (iid.is_channel && !iid.get_server().empty())
+ { // presence toward a MUC that corresponds to an irc channel, or a
+ // dummy channel if iid.chan is empty
+ if (type.empty())
+ {
+ const std::string own_nick = bridge->get_own_nick(iid);
+ if (!own_nick.empty() && own_nick != to.resource)
+ bridge->send_irc_nick_change(iid, to.resource);
+ const XmlNode* x = stanza.get_child("x", MUC_NS);
+ const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
+ bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
+ from.resource);
+ }
+ else if (type == "unavailable")
+ {
+ const XmlNode* status = stanza.get_child("status", COMPONENT_NS);
+ bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource);
+ }
+ }
+ else
+ {
+ // An user wants to join an invalid IRC channel, return a presence error to him
+ if (type.empty())
+ this->send_invalid_room_error(to.local, to.resource, from_str);
+ }
+ }
+ catch (const IRCNotConnected& ex)
+ {
+ this->send_stanza_error("presence", from_str, to_str, id,
+ "cancel", "remote-server-not-found",
+ "Not connected to IRC server "s + ex.hostname,
+ true);
+ }
+ stanza_error.disable();
+}
+
+void BiboumiComponent::handle_message(const Stanza& stanza)
+{
+ std::string from = stanza.get_tag("from");
+ std::string id = stanza.get_tag("id");
+ std::string to_str = stanza.get_tag("to");
+ std::string type = stanza.get_tag("type");
+
+ if (from.empty())
+ return;
+ if (type.empty())
+ type = "normal";
+ Bridge* bridge = this->get_user_bridge(from);
+ Jid to(to_str);
+ Iid iid(to.local);
+
+ std::string error_type("cancel");
+ std::string error_name("internal-server-error");
+ utils::ScopeGuard stanza_error([&](){
+ this->send_stanza_error("message", from, to_str, id,
+ error_type, error_name, "");
+ });
+ const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
+
+ try { // catch IRCNotConnected exceptions
+ if (type == "groupchat" && iid.is_channel)
+ {
+ if (body && !body->get_inner().empty())
+ {
+ bridge->send_channel_message(iid, body->get_inner());
+ }
+ const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
+ if (subject)
+ bridge->set_channel_topic(iid, subject->get_inner());
+ }
+ else if (type == "error")
+ {
+ const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
+ // Only a set of errors are considered “fatal”. If we encounter one of
+ // them, we purge (we disconnect the user from all the IRC servers).
+ // We consider this to be true, unless the error condition is
+ // specified and is not in the kickable_errors set
+ bool kickable_error = true;
+ if (error && error->has_children())
+ {
+ const XmlNode* condition = error->get_last_child();
+ if (kickable_errors.find(condition->get_name()) == kickable_errors.end())
+ kickable_error = false;
+ }
+ if (kickable_error)
+ bridge->shutdown("Error from remote client");
+ }
+ else if (type == "chat")
+ {
+ if (body && !body->get_inner().empty())
+ {
+ // a message for nick!server
+ if (iid.is_user && !iid.get_local().empty())
+ {
+ bridge->send_private_message(iid, body->get_inner());
+ bridge->remove_preferred_from_jid(iid.get_local());
+ }
+ else if (!iid.is_user && !to.resource.empty())
+ { // a message for chan%server@biboumi/Nick or
+ // server@biboumi/Nick
+ // Convert that into a message to nick!server
+ Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server());
+ bridge->send_private_message(user_iid, body->get_inner());
+ bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
+ }
+ else if (!iid.is_user && !iid.is_channel)
+ { // Message sent to the server JID
+ // Convert the message body into a raw IRC message
+ bridge->send_raw_message(iid.get_server(), body->get_inner());
+ }
+ }
+ }
+ else if (iid.is_user)
+ this->send_invalid_user_error(to.local, from);
+ } catch (const IRCNotConnected& ex)
+ {
+ this->send_stanza_error("message", from, to_str, id,
+ "cancel", "remote-server-not-found",
+ "Not connected to IRC server "s + ex.hostname,
+ true);
+ }
+ stanza_error.disable();
+}
+
+// We MUST return an iq, whatever happens, except if the type is
+// "result".
+// To do this, we use a scopeguard. If an exception is raised somewhere, an
+// iq of type error "internal-server-error" is sent. If we handle the
+// request properly (by calling a function that registers an iq to be sent
+// later, or that directly sends an iq), we disable the ScopeGuard. If we
+// reach the end of the function without having disabled the scopeguard, we
+// send a "feature-not-implemented" iq as a result. If an other kind of
+// error is found (for example the feature is implemented in biboumi, but
+// the request is missing some attribute) we can just change the values of
+// error_type and error_name and return from the function (without disabling
+// the scopeguard); an iq error will be sent
+void BiboumiComponent::handle_iq(const Stanza& stanza)
+{
+ std::string id = stanza.get_tag("id");
+ std::string from = stanza.get_tag("from");
+ std::string to_str = stanza.get_tag("to");
+ std::string type = stanza.get_tag("type");
+
+ if (from.empty()) {
+ log_warning("Received an iq without a 'from'. Ignoring.");
+ return;
+ }
+ if (id.empty() || to_str.empty() || type.empty())
+ {
+ this->send_stanza_error("iq", from, this->served_hostname, id,
+ "modify", "bad-request", "");
+ return;
+ }
+
+ Bridge* bridge = this->get_user_bridge(from);
+ Jid to(to_str);
+
+ // These two values will be used in the error iq sent if we don't disable
+ // the scopeguard.
+ std::string error_type("cancel");
+ std::string error_name("internal-server-error");
+ utils::ScopeGuard stanza_error([&](){
+ this->send_stanza_error("iq", from, to_str, id,
+ error_type, error_name, "");
+ });
+ try {
+ if (type == "set")
+ {
+ const XmlNode* query;
+ if ((query = stanza.get_child("query", MUC_ADMIN_NS)))
+ {
+ const XmlNode* child = query->get_child("item", MUC_ADMIN_NS);
+ if (child)
+ {
+ std::string nick = child->get_tag("nick");
+ std::string role = child->get_tag("role");
+ std::string affiliation = child->get_tag("affiliation");
+ if (!nick.empty())
+ {
+ Iid iid(to.local);
+ if (role == "none")
+ { // This is a kick
+ std::string reason;
+ const XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
+ if (reason_el)
+ reason = reason_el->get_inner();
+ bridge->send_irc_kick(iid, nick, reason, id, from);
+ }
+ else
+ bridge->forward_affiliation_role_change(iid, nick, affiliation, role);
+ stanza_error.disable();
+ }
+ }
+ }
+ else if ((query = stanza.get_child("command", ADHOC_NS)))
+ {
+ Stanza response("iq");
+ response["to"] = from;
+ response["from"] = to_str;
+ response["id"] = id;
+
+ // Depending on the 'to' jid in the request, we use one adhoc
+ // command handler or an other
+ Iid iid(to.local);
+ AdhocCommandsHandler* adhoc_handler;
+ if (!to.local.empty() && !iid.is_user && !iid.is_channel)
+ adhoc_handler = &this->irc_server_adhoc_commands_handler;
+ else if (!to.local.empty() && iid.is_channel)
+ adhoc_handler = &this->irc_channel_adhoc_commands_handler;
+ else
+ adhoc_handler = &this->adhoc_commands_handler;
+
+ // Execute the command, if any, and get a result XmlNode that we
+ // insert in our response
+ XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query);
+ if (inner_node.get_child("error", ADHOC_NS))
+ response["type"] = "error";
+ else
+ response["type"] = "result";
+ response.add_child(std::move(inner_node));
+ this->send_stanza(response);
+ stanza_error.disable();
+ }
+ }
+ else if (type == "get")
+ {
+ const XmlNode* query;
+ if ((query = stanza.get_child("query", DISCO_INFO_NS)))
+ { // Disco info
+ if (to_str == this->served_hostname)
+ {
+ const std::string node = query->get_tag("node");
+ if (node.empty())
+ {
+ // On the gateway itself
+ this->send_self_disco_info(id, from);
+ stanza_error.disable();
+ }
+ }
+ }
+ else if ((query = stanza.get_child("query", VERSION_NS)))
+ {
+ Iid iid(to.local);
+ if (iid.is_user ||
+ (iid.is_channel && !to.resource.empty()))
+ {
+ // Get the IRC user version
+ std::string target;
+ if (iid.is_user)
+ target = iid.get_local();
+ else
+ target = to.resource;
+ bridge->send_irc_version_request(iid.get_server(), target, id,
+ from, to_str);
+ }
+ else
+ {
+ // On the gateway itself or on a channel
+ this->send_version(id, from, to_str);
+ }
+ stanza_error.disable();
+ }
+ else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
+ {
+ Iid iid(to.local);
+ const std::string node = query->get_tag("node");
+ if (node == ADHOC_NS)
+ {
+ Jid from_jid(from);
+ if (to.local.empty())
+ { // Get biboumi's adhoc commands
+ this->send_adhoc_commands_list(id, from, this->served_hostname,
+ (Config::get("admin", "") ==
+ from_jid.bare()),
+ this->adhoc_commands_handler);
+ stanza_error.disable();
+ }
+ else if (!iid.is_user && !iid.is_channel)
+ { // Get the server's adhoc commands
+ this->send_adhoc_commands_list(id, from, to_str,
+ (Config::get("admin", "") ==
+ from_jid.bare()),
+ this->irc_server_adhoc_commands_handler);
+ stanza_error.disable();
+ }
+ else if (!iid.is_user && iid.is_channel)
+ { // Get the channel's adhoc commands
+ this->send_adhoc_commands_list(id, from, to_str,
+ (Config::get("admin", "") ==
+ from_jid.bare()),
+ this->irc_channel_adhoc_commands_handler);
+ stanza_error.disable();
+ }
+ }
+ else if (node.empty() && !iid.is_user && !iid.is_channel)
+ { // Disco on an IRC server: get the list of channels
+ bridge->send_irc_channel_list_request(iid, id, from);
+ stanza_error.disable();
+ }
+ }
+ else if ((query = stanza.get_child("ping", PING_NS)))
+ {
+ Iid iid(to.local);
+ if (iid.is_user)
+ { // Ping any user (no check on the nick done ourself)
+ bridge->send_irc_user_ping_request(iid.get_server(),
+ iid.get_local(), id, from, to_str);
+ }
+ else if (iid.is_channel && !to.resource.empty())
+ { // Ping a room participant (we check if the nick is in the room)
+ bridge->send_irc_participant_ping_request(iid,
+ to.resource, id, from, to_str);
+ }
+ else
+ { // Ping a channel, a server or the gateway itself
+ bridge->on_gateway_ping(iid.get_server(),
+ id, from, to_str);
+ }
+ stanza_error.disable();
+ }
+ }
+ else if (type == "result")
+ {
+ stanza_error.disable();
+ const XmlNode* query;
+ if ((query = stanza.get_child("query", VERSION_NS)))
+ {
+ const XmlNode* name_node = query->get_child("name", VERSION_NS);
+ const XmlNode* version_node = query->get_child("version", VERSION_NS);
+ const XmlNode* os_node = query->get_child("os", VERSION_NS);
+ std::string name;
+ std::string version;
+ std::string os;
+ if (name_node)
+ name = name_node->get_inner() + " (through the biboumi gateway)";
+ if (version_node)
+ version = version_node->get_inner();
+ if (os_node)
+ os = os_node->get_inner();
+ const Iid iid(to.local);
+ bridge->send_xmpp_version_to_irc(iid, name, version, os);
+ }
+ else
+ {
+ const auto it = this->waiting_iq.find(id);
+ if (it != this->waiting_iq.end())
+ {
+ it->second(bridge, stanza);
+ this->waiting_iq.erase(it);
+ }
+ }
+ }
+ }
+ catch (const IRCNotConnected& ex)
+ {
+ this->send_stanza_error("iq", from, to_str, id,
+ "cancel", "remote-server-not-found",
+ "Not connected to IRC server "s + ex.hostname,
+ true);
+ stanza_error.disable();
+ return;
+ }
+ error_type = "cancel";
+ error_name = "feature-not-implemented";
+}
+
+Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
+{
+ auto bare_jid = Jid{user_jid}.bare();
+ try
+ {
+ return this->bridges.at(bare_jid).get();
+ }
+ catch (const std::out_of_range& exception)
+ {
+ this->bridges.emplace(bare_jid, std::make_unique<Bridge>(bare_jid, *this, this->poller));
+ return this->bridges.at(bare_jid).get();
+ }
+}
+
+Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid)
+{
+ auto bare_jid = Jid{full_jid}.bare();
+ try
+ {
+ return this->bridges.at(bare_jid).get();
+ }
+ catch (const std::out_of_range& exception)
+ {
+ return nullptr;
+ }
+}
+
+std::vector<Bridge*> BiboumiComponent::get_bridges() const
+{
+ std::vector<Bridge*> res;
+ for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
+ res.push_back(it->second.get());
+ return res;
+}
+
+void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to)
+{
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["to"] = jid_to;
+ iq["from"] = this->served_hostname;
+ XmlNode query("query");
+ query["xmlns"] = DISCO_INFO_NS;
+ XmlNode identity("identity");
+ identity["category"] = "conference";
+ identity["type"] = "irc";
+ identity["name"] = "Biboumi XMPP-IRC gateway";
+ query.add_child(std::move(identity));
+ for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
+ {
+ XmlNode feature("feature");
+ feature["var"] = ns;
+ query.add_child(std::move(feature));
+ }
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
+
+void BiboumiComponent::send_iq_version_request(const std::string& from,
+ const std::string& jid_to)
+{
+ Stanza iq("iq");
+ iq["type"] = "get";
+ iq["id"] = "version_"s + this->next_id();
+ iq["from"] = from + "@" + this->served_hostname;
+ iq["to"] = jid_to;
+ XmlNode query("query");
+ query["xmlns"] = VERSION_NS;
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
+
+void BiboumiComponent::send_ping_request(const std::string& from,
+ const std::string& jid_to,
+ const std::string& id)
+{
+ Stanza iq("iq");
+ iq["type"] = "get";
+ iq["id"] = id;
+ iq["from"] = from + "@" + this->served_hostname;
+ iq["to"] = jid_to;
+ XmlNode ping("ping");
+ ping["xmlns"] = PING_NS;
+ iq.add_child(std::move(ping));
+ this->send_stanza(iq);
+
+ auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza)
+ {
+ Jid to(stanza.get_tag("to"));
+ if (to.local != from)
+ {
+ log_error("Received a corresponding ping result, but the 'to' from "
+ "the response mismatches the 'from' of the request");
+ }
+ else
+ bridge->send_irc_ping_result(from, id);
+ };
+ this->waiting_iq[id] = result_cb;
+}
+
+void BiboumiComponent::send_iq_room_list_result(const std::string& id,
+ const std::string& to_jid,
+ const std::string& from,
+ const std::vector<ListElement>& rooms_list)
+{
+ Stanza iq("iq");
+ iq["from"] = from + "@" + this->served_hostname;
+ iq["to"] = to_jid;
+ iq["id"] = id;
+ iq["type"] = "result";
+ XmlNode query("query");
+ query["xmlns"] = DISCO_ITEMS_NS;
+ for (const auto& room: rooms_list)
+ {
+ XmlNode item("item");
+ item["jid"] = room.channel + "%" + from + "@" + this->served_hostname;
+ query.add_child(std::move(item));
+ }
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
new file mode 100644
index 0000000..24d768a
--- /dev/null
+++ b/src/xmpp/biboumi_component.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+
+#include <xmpp/xmpp_component.hpp>
+
+#include <bridge/bridge.hpp>
+
+#include <memory>
+#include <string>
+#include <map>
+
+struct ListElement;
+
+/**
+ * A callback called when the waited iq result is received (it is matched
+ * against the iq id)
+ */
+using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza& stanza)>;
+
+/**
+ * Interact with the Biboumi Bridge
+ */
+class BiboumiComponent: public XmppComponent
+{
+public:
+ explicit BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
+ ~BiboumiComponent() = default;
+
+ BiboumiComponent(const BiboumiComponent&) = delete;
+ BiboumiComponent(BiboumiComponent&&) = delete;
+ BiboumiComponent& operator=(const BiboumiComponent&) = delete;
+ BiboumiComponent& operator=(BiboumiComponent&&) = delete;
+
+ /**
+ * Returns the bridge for the given user. If it does not exist, return
+ * nullptr.
+ */
+ Bridge* find_user_bridge(const std::string& full_jid);
+ /**
+ * Return a list of all the managed bridges.
+ */
+ std::vector<Bridge*> get_bridges() const;
+
+ /**
+ * Send a "close" message to all our connected peers. That message
+ * depends on the protocol used (this may be a QUIT irc message, or a
+ * <stream/>, etc). We may also directly close the connection, or we may
+ * wait for the remote peer to acknowledge it before closing.
+ */
+ void shutdown();
+ /**
+ * Run a check on all bridges, to remove all disconnected (socket is
+ * closed, or no channel is joined) IrcClients. Some kind of garbage collector.
+ */
+ void clean();
+ /**
+ * Send a result IQ with the gateway disco informations.
+ */
+ void send_self_disco_info(const std::string& id, const std::string& jid_to);
+ /**
+ * Send an iq version request
+ */
+ void send_iq_version_request(const std::string& from,
+ const std::string& jid_to);
+ /**
+ * Send a ping request
+ */
+ void send_ping_request(const std::string& from,
+ const std::string& jid_to,
+ const std::string& id);
+ /**
+ * Send the channels list in one big stanza
+ */
+ void send_iq_room_list_result(const std::string& id, const std::string& to_jid,
+ const std::string& from,
+ const std::vector<ListElement>& rooms_list);
+ /**
+ * Handle the various stanza types
+ */
+ void handle_presence(const Stanza& stanza);
+ void handle_message(const Stanza& stanza);
+ void handle_iq(const Stanza& stanza);
+
+private:
+ /**
+ * Return the bridge associated with the bare JID. Create a new one
+ * if none already exist.
+ */
+ Bridge* get_user_bridge(const std::string& user_jid);
+
+ /**
+ * A map of id -> callback. When we want to wait for an iq result, we add
+ * the callback to this map, with the iq id as the key. When an iq result
+ * is received, we look for a corresponding callback in this map. If
+ * found, we call it and remove it.
+ */
+ std::map<std::string, iq_responder_callback_t> waiting_iq;
+
+ /**
+ * One bridge for each user of the component. Indexed by the user's bare
+ * jid
+ */
+ std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges;
+
+ AdhocCommandsHandler irc_server_adhoc_commands_handler;
+ AdhocCommandsHandler irc_channel_adhoc_commands_handler;
+};
+
+