summaryrefslogtreecommitdiff
path: root/src/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmpp')
-rw-r--r--src/xmpp/adhoc_command.cpp10
-rw-r--r--src/xmpp/adhoc_commands_handler.cpp143
-rw-r--r--src/xmpp/adhoc_commands_handler.hpp69
-rw-r--r--src/xmpp/adhoc_session.cpp37
-rw-r--r--src/xmpp/adhoc_session.hpp70
-rw-r--r--src/xmpp/biboumi_component.cpp568
-rw-r--r--src/xmpp/biboumi_component.hpp111
-rw-r--r--src/xmpp/jid.cpp2
-rw-r--r--src/xmpp/xmpp_component.cpp1194
-rw-r--r--src/xmpp/xmpp_component.hpp303
10 files changed, 687 insertions, 1820 deletions
diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp
index c4e8a44..ba20eba 100644
--- a/src/xmpp/adhoc_command.cpp
+++ b/src/xmpp/adhoc_command.cpp
@@ -1,5 +1,5 @@
#include <xmpp/adhoc_command.hpp>
-#include <xmpp/xmpp_component.hpp>
+#include <xmpp/biboumi_component.hpp>
#include <bridge/bridge.hpp>
@@ -98,6 +98,8 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node)
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");
@@ -115,7 +117,7 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode&
XmlNode required("required");
required.close();
jids_field.add_child(std::move(required));
- for (Bridge* bridge: xmpp_component->get_bridges())
+ for (Bridge* bridge: biboumi_component->get_bridges())
{
XmlNode option("option");
option["label"] = bridge->get_jid();
@@ -145,6 +147,8 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode&
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;
XmlNode* x = command_node.get_child("x", "jabber:x:data");
@@ -168,7 +172,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X
std::size_t num = 0;
for (XmlNode* value: jids_field->get_children("value", "jabber:x:data"))
{
- Bridge* bridge = xmpp_component->find_user_bridge(value->get_inner());
+ Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner());
if (bridge)
{
bridge->shutdown(quit_message);
diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp
deleted file mode 100644
index def1dcb..0000000
--- a/src/xmpp/adhoc_commands_handler.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-#include <xmpp/adhoc_commands_handler.hpp>
-#include <xmpp/xmpp_component.hpp>
-
-#include <utils/timed_events.hpp>
-#include <logger/logger.hpp>
-#include <config/config.hpp>
-#include <xmpp/jid.hpp>
-
-#include <iostream>
-
-using namespace std::string_literals;
-
-AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component):
- xmpp_component(xmpp_component),
- 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)},
- {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
- }
-{
-}
-
-AdhocCommandsHandler::~AdhocCommandsHandler()
-{
-}
-
-const std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get_commands() const
-{
- return this->commands;
-}
-
-XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node)
-{
- std::string action = command_node.get_tag("action");
- if (action.empty())
- action = "execute";
- command_node.del_tag("action");
-
- Jid jid(executor_jid);
-
- const std::string node = command_node.get_tag("node");
- auto command_it = this->commands.find(node);
- if (command_it == this->commands.end())
- {
- XmlNode error(ADHOC_NS":error");
- error["type"] = "cancel";
- XmlNode condition(STANZA_NS":item-not-found");
- condition.close();
- error.add_child(std::move(condition));
- error.close();
- command_node.add_child(std::move(error));
- }
- else if (command_it->second.is_admin_only() &&
- Config::get("admin", "") != jid.local + "@" + jid.domain)
- {
- XmlNode error(ADHOC_NS":error");
- error["type"] = "cancel";
- XmlNode condition(STANZA_NS":forbidden");
- condition.close();
- error.add_child(std::move(condition));
- error.close();
- command_node.add_child(std::move(error));
- }
- else
- {
- std::string sessionid = command_node.get_tag("sessionid");
- if (sessionid.empty())
- { // create a new session, with a new id
- sessionid = XmppComponent::next_id();
- command_node["sessionid"] = sessionid;
- this->sessions.emplace(std::piecewise_construct,
- std::forward_as_tuple(sessionid, executor_jid),
- std::forward_as_tuple(command_it->second, executor_jid));
- TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s,
- std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid),
- "adhocsession"s + sessionid + executor_jid));
- }
- auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid));
- if (session_it == this->sessions.end())
- {
- XmlNode error(ADHOC_NS":error");
- error["type"] = "modify";
- XmlNode condition(STANZA_NS":bad-request");
- condition.close();
- error.add_child(std::move(condition));
- error.close();
- command_node.add_child(std::move(error));
- }
- else if (action == "execute" || action == "next" || action == "complete")
- {
- // execute the step
- AdhocSession& session = session_it->second;
- const AdhocStep& step = session.get_next_step();
- step(this->xmpp_component, session, command_node);
- if (session.remaining_steps() == 0 ||
- session.is_terminated())
- {
- this->sessions.erase(session_it);
- command_node["status"] = "completed";
- TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid);
- }
- else
- {
- command_node["status"] = "executing";
- XmlNode actions("actions");
- XmlNode next("next");
- next.close();
- actions.add_child(std::move(next));
- actions.close();
- command_node.add_child(std::move(actions));
- }
- }
- else if (action == "cancel")
- {
- this->sessions.erase(session_it);
- command_node["status"] = "canceled";
- TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid);
- }
- else // unsupported action
- {
- XmlNode error(ADHOC_NS":error");
- error["type"] = "modify";
- XmlNode condition(STANZA_NS":bad-request");
- condition.close();
- error.add_child(std::move(condition));
- error.close();
- command_node.add_child(std::move(error));
- }
- }
- return command_node;
-}
-
-void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid)
-{
- auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid));
- if (session_it != this->sessions.end())
- {
- this->sessions.erase(session_it);
- return ;
- }
- log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found");
-}
diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp
deleted file mode 100644
index 7ddad47..0000000
--- a/src/xmpp/adhoc_commands_handler.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef ADHOC_COMMANDS_HANDLER_HPP
-# define ADHOC_COMMANDS_HANDLER_HPP
-
-/**
- * Manage a list of available AdhocCommands and the list of ongoing
- * AdhocCommandSessions.
- */
-
-#include <xmpp/adhoc_command.hpp>
-#include <xmpp/xmpp_stanza.hpp>
-
-#include <utility>
-#include <string>
-#include <map>
-
-class XmppComponent;
-
-class AdhocCommandsHandler
-{
-public:
- explicit AdhocCommandsHandler(XmppComponent* xmpp_component);
- ~AdhocCommandsHandler();
- /**
- * Returns the list of available commands.
- */
- const std::map<const std::string, const AdhocCommand>& get_commands() const;
- /**
- * Find the requested command, create a new session or use an existing
- * one, and process the request (provide a new form, an error, or a
- * result).
- *
- * Returns a (moved) XmlNode that will be inserted in the iq response. It
- * should be a <command/> node containing one or more useful children. If
- * it contains an <error/> node, the iq response will have an error type.
- *
- * Takes a copy of the <command/> node so we can actually edit it and use
- * it as our return value.
- */
- XmlNode handle_request(const std::string& executor_jid, XmlNode command_node);
- /**
- * Remove the session from the list. This is done to avoid filling the
- * memory with waiting session (for example due to a client that starts
- * multi-steps command but never finishes them).
- */
- void remove_session(const std::string& session_id, const std::string& initiator_jid);
-private:
- /**
- * A pointer to the XmppComponent, to access to basically anything in the
- * gateway.
- */
- XmppComponent* xmpp_component;
- /**
- * The list of all available commands.
- */
- const std::map<const std::string, const AdhocCommand> commands;
- /**
- * The list of all currently on-going commands.
- *
- * Of the form: {{session_id, owner_jid}, session}.
- */
- std::map<std::pair<const std::string, const std::string>, AdhocSession> sessions;
-
- AdhocCommandsHandler(const AdhocCommandsHandler&) = delete;
- AdhocCommandsHandler(AdhocCommandsHandler&&) = delete;
- AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete;
- AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete;
-};
-
-#endif // ADHOC_COMMANDS_HANDLER_HPP
diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp
deleted file mode 100644
index fc60bb7..0000000
--- a/src/xmpp/adhoc_session.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#include <xmpp/adhoc_session.hpp>
-#include <xmpp/adhoc_command.hpp>
-
-#include <assert.h>
-
-AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid):
- command(command),
- owner_jid(jid),
- current_step(0),
- terminated(false)
-{
-}
-
-AdhocSession::~AdhocSession()
-{
-}
-
-const AdhocStep& AdhocSession::get_next_step()
-{
- assert(this->current_step < this->command.callbacks.size());
- return this->command.callbacks[this->current_step++];
-}
-
-size_t AdhocSession::remaining_steps() const
-{
- return this->command.callbacks.size() - this->current_step;
-}
-
-bool AdhocSession::is_terminated() const
-{
- return this->terminated;
-}
-
-void AdhocSession::terminate()
-{
- this->terminated = true;
-}
diff --git a/src/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp
deleted file mode 100644
index ddfb2fe..0000000
--- a/src/xmpp/adhoc_session.hpp
+++ /dev/null
@@ -1,70 +0,0 @@
-#ifndef ADHOC_SESSION_HPP
-# define ADHOC_SESSION_HPP
-
-#include <xmpp/xmpp_stanza.hpp>
-
-#include <functional>
-#include <string>
-
-class XmppComponent;
-
-class AdhocCommand;
-class AdhocSession;
-
-/**
- * A function executed as an ad-hoc command step. It takes a <command/>
- * XmlNode and modifies it accordingly (inserting for example an <error/>
- * node, or a data form…).
- * TODO fix this:
- * It also must call one of step_passed(), cancel() etc on the AdhocSession object.
- */
-typedef std::function<void(XmppComponent*, AdhocSession&, XmlNode&)> AdhocStep;
-
-class AdhocSession
-{
-public:
- explicit AdhocSession(const AdhocCommand& command, const std::string& jid);
- ~AdhocSession();
- /**
- * Return the function to be executed, found in our AdhocCommand, for the
- * current_step. And increment the current_step.
- */
- const AdhocStep& get_next_step();
- /**
- * Return the number of remaining steps.
- */
- size_t remaining_steps() const;
- /**
- * This may be modified by an AdhocStep, to indicate that this session
- * should no longer exist, because we encountered an error, and we can't
- * execute any more step of it.
- */
- void terminate();
- bool is_terminated() const;
-
-private:
- /**
- * A reference of the command concerned by this session. Used for example
- * to get the next step of that command, things like that.
- */
- const AdhocCommand& command;
- /**
- * The full JID of the XMPP user that created this session by executing
- * the first step of a command. Only that JID must be allowed to access
- * this session.
- */
- const std::string& owner_jid;
- /**
- * The current step we are at. It starts at zero. It is used to index the
- * associated AdhocCommand::callbacks vector.
- */
- size_t current_step;
- bool terminated;
-
- AdhocSession(const AdhocSession&) = delete;
- AdhocSession(AdhocSession&&) = delete;
- AdhocSession& operator=(const AdhocSession&) = delete;
- AdhocSession& operator=(AdhocSession&&) = delete;
-};
-
-#endif // ADHOC_SESSION_HPP
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
new file mode 100644
index 0000000..2ecf247
--- /dev/null
+++ b/src/xmpp/biboumi_component.cpp
@@ -0,0 +1,568 @@
+#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 <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 <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)
+{
+ 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 a user from the gateway", true)},
+ {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
+ };
+}
+
+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 = 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.empty())
+ {
+ log_warning("Received an invalid presence stanza: tag 'from' is missing.");
+ return;
+ }
+ if (to_str.empty())
+ {
+ this->send_stanza_error("presence", from, this->served_hostname, id,
+ "modify", "bad-request", "Missing 'to' tag");
+ return;
+ }
+
+ Bridge* bridge = this->get_user_bridge(from);
+ Jid to(to_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, to_str, id,
+ error_type, error_name, "");
+ });
+
+ 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);
+ XmlNode* x = stanza.get_child("x", MUC_NS);
+ XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
+ bridge->join_irc_channel(iid, to.resource,
+ password ? password->get_inner() : "");
+ }
+ else if (type == "unavailable")
+ {
+ XmlNode* status = stanza.get_child("status", COMPONENT_NS);
+ bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
+ }
+ }
+ 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);
+ }
+ 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, "");
+ });
+ XmlNode* body = stanza.get_child("body", COMPONENT_NS);
+ if (type == "groupchat" && iid.is_channel)
+ {
+ if (body && !body->get_inner().empty())
+ {
+ bridge->send_channel_message(iid, body->get_inner());
+ }
+ 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)
+ this->send_invalid_user_error(to.local, from);
+ 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())
+ 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, "");
+ });
+ if (type == "set")
+ {
+ 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;
+ 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"] = this->served_hostname;
+ response["id"] = id;
+ XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query);
+ if (inner_node.get_child("error", ADHOC_NS))
+ response["type"] = "error";
+ else
+ response["type"] = "result";
+ response.add_child(std::move(inner_node));
+ response.close();
+ this->send_stanza(response);
+ stanza_error.disable();
+ }
+ }
+ else if (type == "get")
+ {
+ 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)
+ {
+ this->send_adhoc_commands_list(id, from);
+ 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();
+ XmlNode* query;
+ if ((query = stanza.get_child("query", VERSION_NS)))
+ {
+ XmlNode* name_node = query->get_child("name", VERSION_NS);
+ XmlNode* version_node = query->get_child("version", VERSION_NS);
+ 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);
+ }
+ }
+ }
+ error_type = "cancel";
+ error_name = "feature-not-implemented";
+}
+
+Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
+{
+ try
+ {
+ return this->bridges.at(user_jid).get();
+ }
+ catch (const std::out_of_range& exception)
+ {
+ this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller));
+ return this->bridges.at(user_jid).get();
+ }
+}
+
+Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid)
+{
+ try
+ {
+ return this->bridges.at(user_jid).get();
+ }
+ catch (const std::out_of_range& exception)
+ {
+ return nullptr;
+ }
+}
+
+std::list<Bridge*> BiboumiComponent::get_bridges() const
+{
+ std::list<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";
+ identity.close();
+ query.add_child(std::move(identity));
+ for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
+ {
+ XmlNode feature("feature");
+ feature["var"] = ns;
+ feature.close();
+ query.add_child(std::move(feature));
+ }
+ query.close();
+ iq.add_child(std::move(query));
+ iq.close();
+ this->send_stanza(iq);
+}
+
+void BiboumiComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid)
+{
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["to"] = requester_jid;
+ iq["from"] = this->served_hostname;
+ XmlNode query("query");
+ query["xmlns"] = DISCO_ITEMS_NS;
+ query["node"] = ADHOC_NS;
+ for (const auto& kv: this->adhoc_commands_handler.get_commands())
+ {
+ XmlNode item("item");
+ item["jid"] = this->served_hostname;
+ item["node"] = kv.first;
+ item["name"] = kv.second.name;
+ item.close();
+ query.add_child(std::move(item));
+ }
+ query.close();
+ iq.add_child(std::move(query));
+ iq.close();
+ 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;
+ query.close();
+ iq.add_child(std::move(query));
+ iq.close();
+ 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;
+ ping.close();
+ iq.add_child(std::move(ping));
+ iq.close();
+ 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;
+ item.close();
+ query.add_child(std::move(item));
+ }
+ query.close();
+ iq.add_child(std::move(query));
+ iq.close();
+ this->send_stanza(iq);
+}
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
new file mode 100644
index 0000000..e9b5d72
--- /dev/null
+++ b/src/xmpp/biboumi_component.hpp
@@ -0,0 +1,111 @@
+#ifndef BIBOUMI_COMPONENT_INCLUDED
+# define BIBOUMI_COMPONENT_INCLUDED
+
+#include <xmpp/xmpp_component.hpp>
+
+#include <bridge/bridge.hpp>
+
+#include <memory>
+#include <string>
+#include <map>
+
+class 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;
+
+ /**
+ * Returns the bridge for the given user. If it does not exist, return
+ * nullptr.
+ */
+ Bridge* find_user_bridge(const std::string& user_jid);
+ /**
+ * Return a list of all the managed bridges.
+ */
+ std::list<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 the list of all available ad-hoc commands to that JID. The list is
+ * different depending on what JID made the request.
+ */
+ void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid);
+ /**
+ * 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 given full 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 full
+ * jid
+ */
+ std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges;
+
+ BiboumiComponent(const BiboumiComponent&) = delete;
+ BiboumiComponent(BiboumiComponent&&) = delete;
+ BiboumiComponent& operator=(const BiboumiComponent&) = delete;
+ BiboumiComponent& operator=(BiboumiComponent&&) = delete;
+};
+
+#endif // BIBOUMI_COMPONENT_INCLUDED
diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp
index c51e011..6149ceb 100644
--- a/src/xmpp/jid.cpp
+++ b/src/xmpp/jid.cpp
@@ -1,8 +1,8 @@
#include <xmpp/jid.hpp>
-#include <config.h>
#include <cstring>
#include <map>
+#include <louloulibs.h>
#ifdef LIBIDN_FOUND
#include <stringprep.h>
#endif
diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp
deleted file mode 100644
index 0e2531d..0000000
--- a/src/xmpp/xmpp_component.cpp
+++ /dev/null
@@ -1,1194 +0,0 @@
-#include <utils/timed_events.hpp>
-#include <utils/scopeguard.hpp>
-#include <utils/tolower.hpp>
-#include <logger/logger.hpp>
-
-#include <xmpp/xmpp_component.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 <config.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"
- };
-
-XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
- TCPSocketHandler(poller),
- ever_auth(false),
- first_connection_try(true),
- served_hostname(hostname),
- secret(secret),
- authenticated(false),
- doc_open(false),
- adhoc_commands_handler(this)
-{
- this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this,
- std::placeholders::_1));
- this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this,
- std::placeholders::_1));
- this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this,
- std::placeholders::_1));
- this->stanza_handlers.emplace("handshake",
- std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1));
- this->stanza_handlers.emplace("presence",
- std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1));
- this->stanza_handlers.emplace("message",
- std::bind(&XmppComponent::handle_message, this,std::placeholders::_1));
- this->stanza_handlers.emplace("iq",
- std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1));
- this->stanza_handlers.emplace("error",
- std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
-}
-
-XmppComponent::~XmppComponent()
-{
-}
-
-void XmppComponent::start()
-{
- this->connect("localhost", Config::get("port", "5347"), false);
-}
-
-bool XmppComponent::is_document_open() const
-{
- return this->doc_open;
-}
-
-void XmppComponent::send_stanza(const Stanza& stanza)
-{
- std::string str = stanza.to_string();
- log_debug("XMPP SENDING: " << str);
- this->send_data(std::move(str));
-}
-
-void XmppComponent::on_connection_failed(const std::string& reason)
-{
- this->first_connection_try = false;
- log_error("Failed to connect to the XMPP server: " << reason);
-#ifdef SYSTEMD_FOUND
- sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data());
-#endif
-}
-
-void XmppComponent::on_connected()
-{
- log_info("connected to XMPP server");
- this->first_connection_try = true;
- XmlNode node("", nullptr);
- node.set_name("stream:stream");
- node["xmlns"] = COMPONENT_NS;
- node["xmlns:stream"] = STREAM_NS;
- node["to"] = this->served_hostname;
- this->send_stanza(node);
- this->doc_open = true;
- // We may have some pending data to send: this happens when we try to send
- // some data before we are actually connected. We send that data right now, if any
- this->send_pending_data();
-}
-
-void XmppComponent::on_connection_close(const std::string& error)
-{
- if (error.empty())
- {
- log_info("XMPP server closed connection");
- }
- else
- {
- log_info("XMPP server closed connection: " << error);
- }
-}
-
-void XmppComponent::parse_in_buffer(const size_t size)
-{
- if (!this->in_buf.empty())
- { // This may happen if the parser could not allocate enough space for
- // us. We try to feed it the data that was read into our in_buf
- // instead. If this fails again we are in trouble.
- this->parser.feed(this->in_buf.data(), this->in_buf.size(), false);
- this->in_buf.clear();
- }
- else
- { // Just tell the parser to parse the data that was placed into the
- // buffer it provided to us with GetBuffer
- this->parser.parse(size, false);
- }
-}
-
-void XmppComponent::shutdown()
-{
- for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
- {
- it->second->shutdown("Gateway shutdown");
- }
-}
-
-void XmppComponent::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 XmppComponent::on_remote_stream_open(const XmlNode& node)
-{
- log_debug("XMPP DOCUMENT OPEN: " << node.to_string());
- this->stream_id = node.get_tag("id");
- if (this->stream_id.empty())
- {
- log_error("Error: no attribute 'id' found");
- this->send_stream_error("bad-format", "missing 'id' attribute");
- this->close_document();
- return ;
- }
-
- // Try to authenticate
- char digest[HASH_LENGTH * 2 + 1];
- sha1nfo sha1;
- sha1_init(&sha1);
- sha1_write(&sha1, this->stream_id.data(), this->stream_id.size());
- sha1_write(&sha1, this->secret.data(), this->secret.size());
- const uint8_t* result = sha1_result(&sha1);
- for (int i=0; i < HASH_LENGTH; i++)
- sprintf(digest + (i*2), "%02x", result[i]);
- digest[HASH_LENGTH * 2] = '\0';
-
- Stanza handshake(COMPONENT_NS":handshake");
- handshake.set_inner(digest);
- handshake.close();
- this->send_stanza(handshake);
-}
-
-void XmppComponent::on_remote_stream_close(const XmlNode& node)
-{
- log_debug("XMPP DOCUMENT CLOSE " << node.to_string());
- this->doc_open = false;
-}
-
-void XmppComponent::reset()
-{
- this->parser.reset();
-}
-
-void XmppComponent::on_stanza(const Stanza& stanza)
-{
- log_debug("XMPP RECEIVING: " << stanza.to_string());
- std::function<void(const Stanza&)> handler;
- try
- {
- handler = this->stanza_handlers.at(stanza.get_name());
- }
- catch (const std::out_of_range& exception)
- {
- log_warning("No handler for stanza of type " << stanza.get_name());
- return;
- }
- handler(stanza);
-}
-
-void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
-{
- XmlNode node("stream:error", nullptr);
- XmlNode error(name, nullptr);
- error["xmlns"] = STREAM_NS;
- if (!explanation.empty())
- error.set_inner(explanation);
- error.close();
- node.add_child(std::move(error));
- node.close();
- this->send_stanza(node);
-}
-
-void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
- const std::string& id, const std::string& error_type,
- const std::string& defined_condition, const std::string& text,
- const bool fulljid)
-{
- Stanza node(kind);
- if (!to.empty())
- node["to"] = to;
- if (!from.empty())
- {
- if (fulljid)
- node["from"] = from;
- else
- node["from"] = from + "@" + this->served_hostname;
- }
- if (!id.empty())
- node["id"] = id;
- node["type"] = "error";
- XmlNode error("error");
- error["type"] = error_type;
- XmlNode inner_error(defined_condition);
- inner_error["xmlns"] = STANZA_NS;
- inner_error.close();
- error.add_child(std::move(inner_error));
- if (!text.empty())
- {
- XmlNode text_node("text");
- text_node["xmlns"] = STANZA_NS;
- text_node.set_inner(text);
- text_node.close();
- error.add_child(std::move(text_node));
- }
- error.close();
- node.add_child(std::move(error));
- node.close();
- this->send_stanza(node);
-}
-
-void XmppComponent::close_document()
-{
- log_debug("XMPP SENDING: </stream:stream>");
- this->send_data("</stream:stream>");
- this->doc_open = false;
-}
-
-void XmppComponent::handle_handshake(const Stanza& stanza)
-{
- (void)stanza;
- this->authenticated = true;
- this->ever_auth = true;
- log_info("Authenticated with the XMPP server");
-#ifdef SYSTEMD_FOUND
- sd_notify(0, "READY=1");
- // Install an event that sends a keepalive to systemd. If biboumi crashes
- // or hangs for too long, systemd will restart it.
- uint64_t usec;
- if (sd_watchdog_enabled(0, &usec) > 0)
- {
- TimedEventsManager::instance().add_event(TimedEvent(
- std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)),
- []() { sd_notify(0, "WATCHDOG=1"); }));
- }
-#endif
-}
-
-void XmppComponent::handle_presence(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");
-
- // Check for mandatory tags
- if (from.empty())
- {
- log_warning("Received an invalid presence stanza: tag 'from' is missing.");
- return;
- }
- if (to_str.empty())
- {
- this->send_stanza_error("presence", from, this->served_hostname, id,
- "modify", "bad-request", "Missing 'to' tag");
- return;
- }
-
- Bridge* bridge = this->get_user_bridge(from);
- Jid to(to_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, to_str, id,
- error_type, error_name, "");
- });
-
- 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);
- XmlNode* x = stanza.get_child("x", MUC_NS);
- XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
- bridge->join_irc_channel(iid, to.resource,
- password ? password->get_inner() : "");
- }
- else if (type == "unavailable")
- {
- XmlNode* status = stanza.get_child("status", COMPONENT_NS);
- bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
- }
- }
- 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);
- }
- stanza_error.disable();
-}
-
-void XmppComponent::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, "");
- });
- XmlNode* body = stanza.get_child("body", COMPONENT_NS);
- if (type == "groupchat" && iid.is_channel)
- {
- if (body && !body->get_inner().empty())
- {
- bridge->send_channel_message(iid, body->get_inner());
- }
- 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)
- this->send_invalid_user_error(to.local, from);
- 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 XmppComponent::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())
- 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, "");
- });
- if (type == "set")
- {
- 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;
- 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"] = this->served_hostname;
- response["id"] = id;
- XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query);
- if (inner_node.get_child("error", ADHOC_NS))
- response["type"] = "error";
- else
- response["type"] = "result";
- response.add_child(std::move(inner_node));
- response.close();
- this->send_stanza(response);
- stanza_error.disable();
- }
- }
- else if (type == "get")
- {
- 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)
- {
- this->send_adhoc_commands_list(id, from);
- 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();
- XmlNode* query;
- if ((query = stanza.get_child("query", VERSION_NS)))
- {
- XmlNode* name_node = query->get_child("name", VERSION_NS);
- XmlNode* version_node = query->get_child("version", VERSION_NS);
- 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);
- }
- }
- }
- error_type = "cancel";
- error_name = "feature-not-implemented";
-}
-
-void XmppComponent::handle_error(const Stanza& stanza)
-{
- XmlNode* text = stanza.get_child("text", STREAMS_NS);
- std::string error_message("Unspecified error");
- if (text)
- error_message = text->get_inner();
- log_error("Stream error received from the XMPP server: " << error_message);
-#ifdef SYSTEMD_FOUND
- if (!this->ever_auth)
- sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data());
-#endif
-
-}
-
-Bridge* XmppComponent::get_user_bridge(const std::string& user_jid)
-{
- try
- {
- return this->bridges.at(user_jid).get();
- }
- catch (const std::out_of_range& exception)
- {
- this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller));
- return this->bridges.at(user_jid).get();
- }
-}
-
-Bridge* XmppComponent::find_user_bridge(const std::string& user_jid)
-{
- try
- {
- return this->bridges.at(user_jid).get();
- }
- catch (const std::out_of_range& exception)
- {
- return nullptr;
- }
-}
-
-std::list<Bridge*> XmppComponent::get_bridges() const
-{
- std::list<Bridge*> res;
- for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
- res.push_back(it->second.get());
- return res;
-}
-
-void* XmppComponent::get_receive_buffer(const size_t size) const
-{
- return this->parser.get_buffer(size);
-}
-
-void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid)
-{
- XmlNode node("message");
- node["to"] = to;
- if (fulljid)
- node["from"] = from;
- else
- node["from"] = from + "@" + this->served_hostname;
- if (!type.empty())
- node["type"] = type;
- XmlNode body_node("body");
- body_node.set_inner(std::get<0>(body));
- body_node.close();
- node.add_child(std::move(body_node));
- if (std::get<1>(body))
- {
- XmlNode html("html");
- html["xmlns"] = XHTMLIM_NS;
- // Pass the ownership of the pointer to this xmlnode
- html.add_child(std::get<1>(body).release());
- html.close();
- node.add_child(std::move(html));
- }
- node.close();
- this->send_stanza(node);
-}
-
-void XmppComponent::send_user_join(const std::string& from,
- const std::string& nick,
- const std::string& realjid,
- const std::string& affiliation,
- const std::string& role,
- const std::string& to,
- const bool self)
-{
- XmlNode node("presence");
- node["to"] = to;
- node["from"] = from + "@" + this->served_hostname + "/" + nick;
-
- XmlNode x("x");
- x["xmlns"] = MUC_USER_NS;
-
- XmlNode item("item");
- if (!affiliation.empty())
- item["affiliation"] = affiliation;
- if (!role.empty())
- item["role"] = role;
- if (!realjid.empty())
- {
- const std::string preped_jid = jidprep(realjid);
- if (!preped_jid.empty())
- item["jid"] = preped_jid;
- }
- item.close();
- x.add_child(std::move(item));
-
- if (self)
- {
- XmlNode status("status");
- status["code"] = "110";
- status.close();
- x.add_child(std::move(status));
- }
- x.close();
- node.add_child(std::move(x));
- node.close();
- this->send_stanza(node);
-}
-
-void XmppComponent::send_invalid_room_error(const std::string& muc_name,
- const std::string& nick,
- const std::string& to)
-{
- Stanza presence("presence");
- if (!muc_name.empty())
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
- else
- presence["from"] = this->served_hostname;
- presence["to"] = to;
- presence["type"] = "error";
- XmlNode x("x");
- x["xmlns"] = MUC_NS;
- x.close();
- presence.add_child(std::move(x));
- XmlNode error("error");
- error["by"] = muc_name + "@" + this->served_hostname;
- error["type"] = "cancel";
- XmlNode item_not_found("item-not-found");
- item_not_found["xmlns"] = STANZA_NS;
- item_not_found.close();
- error.add_child(std::move(item_not_found));
- XmlNode text("text");
- text["xmlns"] = STANZA_NS;
- text["xml:lang"] = "en";
- text.set_inner(muc_name +
- " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" +
- this->served_hostname);
- text.close();
- error.add_child(std::move(text));
- error.close();
- presence.add_child(std::move(error));
- presence.close();
- this->send_stanza(presence);
-}
-
-void XmppComponent::send_invalid_user_error(const std::string& user_name, const std::string& to)
-{
- Stanza message("message");
- message["from"] = user_name + "@" + this->served_hostname;
- message["to"] = to;
- message["type"] = "error";
- XmlNode x("x");
- x["xmlns"] = MUC_NS;
- x.close();
- message.add_child(std::move(x));
- XmlNode error("error");
- error["type"] = "cancel";
- XmlNode item_not_found("item-not-found");
- item_not_found["xmlns"] = STANZA_NS;
- item_not_found.close();
- error.add_child(std::move(item_not_found));
- XmlNode text("text");
- text["xmlns"] = STANZA_NS;
- text["xml:lang"] = "en";
- text.set_inner(user_name +
- " is not a valid IRC user name. A correct user jid is of the form: <nick>!<server>@" +
- this->served_hostname);
- text.close();
- error.add_child(std::move(text));
- error.close();
- message.add_child(std::move(error));
- message.close();
- this->send_stanza(message);
-}
-
-void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to)
-{
- XmlNode message("message");
- message["to"] = to;
- message["from"] = from + "@" + this->served_hostname;
- message["type"] = "groupchat";
- XmlNode subject("subject");
- subject.set_inner(std::get<0>(topic));
- subject.close();
- message.add_child(std::move(subject));
- message.close();
- this->send_stanza(message);
-}
-
-void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to)
-{
- Stanza message("message");
- message["to"] = jid_to;
- if (!nick.empty())
- message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
- else // Message from the room itself
- message["from"] = muc_name + "@" + this->served_hostname;
- message["type"] = "groupchat";
- XmlNode body("body");
- body.set_inner(std::get<0>(xmpp_body));
- body.close();
- message.add_child(std::move(body));
- if (std::get<1>(xmpp_body))
- {
- XmlNode html("html");
- html["xmlns"] = XHTMLIM_NS;
- // Pass the ownership of the pointer to this xmlnode
- html.add_child(std::get<1>(xmpp_body).release());
- html.close();
- message.add_child(std::move(html));
- }
- message.close();
- this->send_stanza(message);
-}
-
-void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
-{
- Stanza presence("presence");
- presence["to"] = jid_to;
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
- presence["type"] = "unavailable";
- const std::string message_str = std::get<0>(message);
- XmlNode x("x");
- x["xmlns"] = MUC_USER_NS;
- if (self)
- {
- XmlNode status("status");
- status["code"] = "110";
- status.close();
- x.add_child(std::move(status));
- }
- x.close();
- presence.add_child(std::move(x));
- if (!message_str.empty())
- {
- XmlNode status("status");
- status.set_inner(message_str);
- status.close();
- presence.add_child(std::move(status));
- }
- presence.close();
- this->send_stanza(presence);
-}
-
-void XmppComponent::send_nick_change(const std::string& muc_name,
- const std::string& old_nick,
- const std::string& new_nick,
- const std::string& affiliation,
- const std::string& role,
- const std::string& jid_to,
- const bool self)
-{
- Stanza presence("presence");
- presence["to"] = jid_to;
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
- presence["type"] = "unavailable";
- XmlNode x("x");
- x["xmlns"] = MUC_USER_NS;
- XmlNode item("item");
- item["nick"] = new_nick;
- item.close();
- x.add_child(std::move(item));
- XmlNode status("status");
- status["code"] = "303";
- status.close();
- x.add_child(std::move(status));
- if (self)
- {
- XmlNode status2("status");
- status2["code"] = "110";
- status2.close();
- x.add_child(std::move(status2));
- }
- x.close();
- presence.add_child(std::move(x));
- presence.close();
- this->send_stanza(presence);
-
- this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
-}
-
-void XmppComponent::kick_user(const std::string& muc_name,
- const std::string& target,
- const std::string& txt,
- const std::string& author,
- const std::string& jid_to)
-{
- Stanza presence("presence");
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
- presence["to"] = jid_to;
- presence["type"] = "unavailable";
- XmlNode x("x");
- x["xmlns"] = MUC_USER_NS;
- XmlNode item("item");
- item["affiliation"] = "none";
- item["role"] = "none";
- XmlNode actor("actor");
- actor["nick"] = author;
- actor["jid"] = author; // backward compatibility with old clients
- actor.close();
- item.add_child(std::move(actor));
- XmlNode reason("reason");
- reason.set_inner(txt);
- reason.close();
- item.add_child(std::move(reason));
- item.close();
- x.add_child(std::move(item));
- XmlNode status("status");
- status["code"] = "307";
- status.close();
- x.add_child(std::move(status));
- x.close();
- presence.add_child(std::move(x));
- presence.close();
- this->send_stanza(presence);
-}
-
-void XmppComponent::send_presence_error(const std::string& muc_name,
- const std::string& nickname,
- const std::string& jid_to,
- const std::string& type,
- const std::string& condition,
- const std::string& error_code,
- const std::string& /* text */)
-{
- Stanza presence("presence");
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
- presence["to"] = jid_to;
- presence["type"] = "error";
- XmlNode x("x");
- x["xmlns"] = MUC_NS;
- x.close();
- presence.add_child(std::move(x));
- XmlNode error("error");
- error["by"] = muc_name + "@" + this->served_hostname;
- error["type"] = type;
- if (!error_code.empty())
- error["code"] = error_code;
- XmlNode subnode(condition);
- subnode["xmlns"] = STANZA_NS;
- subnode.close();
- error.add_child(std::move(subnode));
- error.close();
- presence.add_child(std::move(error));
- presence.close();
- this->send_stanza(presence);
-}
-
-void XmppComponent::send_affiliation_role_change(const std::string& muc_name,
- const std::string& target,
- const std::string& affiliation,
- const std::string& role,
- const std::string& jid_to)
-{
- Stanza presence("presence");
- presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
- presence["to"] = jid_to;
- XmlNode x("x");
- x["xmlns"] = MUC_USER_NS;
- XmlNode item("item");
- item["affiliation"] = affiliation;
- item["role"] = role;
- item.close();
- x.add_child(std::move(item));
- x.close();
- presence.add_child(std::move(x));
- presence.close();
- this->send_stanza(presence);
-}
-
-void XmppComponent::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";
- identity.close();
- query.add_child(std::move(identity));
- for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
- {
- XmlNode feature("feature");
- feature["var"] = ns;
- feature.close();
- query.add_child(std::move(feature));
- }
- query.close();
- iq.add_child(std::move(query));
- iq.close();
- this->send_stanza(iq);
-}
-
-void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
- const std::string& version)
-{
- Stanza iq("iq");
- iq["type"] = "result";
- iq["id"] = id;
- iq["to"] = jid_to;
- iq["from"] = jid_from;
- XmlNode query("query");
- query["xmlns"] = VERSION_NS;
- if (version.empty())
- {
- XmlNode name("name");
- name.set_inner("biboumi");
- name.close();
- query.add_child(std::move(name));
- XmlNode version("version");
- version.set_inner(BIBOUMI_VERSION);
- version.close();
- query.add_child(std::move(version));
- XmlNode os("os");
- os.set_inner(SYSTEM_NAME);
- os.close();
- query.add_child(std::move(os));
- }
- else
- {
- XmlNode name("name");
- name.set_inner(version);
- name.close();
- query.add_child(std::move(name));
- }
- query.close();
- iq.add_child(std::move(query));
- iq.close();
- this->send_stanza(iq);
-}
-
-void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid)
-{
- Stanza iq("iq");
- iq["type"] = "result";
- iq["id"] = id;
- iq["to"] = requester_jid;
- iq["from"] = this->served_hostname;
- XmlNode query("query");
- query["xmlns"] = DISCO_ITEMS_NS;
- query["node"] = ADHOC_NS;
- for (const auto& kv: this->adhoc_commands_handler.get_commands())
- {
- XmlNode item("item");
- item["jid"] = this->served_hostname;
- item["node"] = kv.first;
- item["name"] = kv.second.name;
- item.close();
- query.add_child(std::move(item));
- }
- query.close();
- iq.add_child(std::move(query));
- iq.close();
- this->send_stanza(iq);
-}
-
-void XmppComponent::send_iq_version_request(const std::string& from,
- const std::string& jid_to)
-{
- Stanza iq("iq");
- iq["type"] = "get";
- iq["id"] = "version_"s + XmppComponent::next_id();
- iq["from"] = from + "@" + this->served_hostname;
- iq["to"] = jid_to;
- XmlNode query("query");
- query["xmlns"] = VERSION_NS;
- query.close();
- iq.add_child(std::move(query));
- iq.close();
- this->send_stanza(iq);
-}
-
-void XmppComponent::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;
- ping.close();
- iq.add_child(std::move(ping));
- iq.close();
- 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 XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part)
-{
- Stanza iq("iq");
- if (!from_local_part.empty())
- iq["from"] = from_local_part + "@" + this->served_hostname;
- else
- iq["from"] = this->served_hostname;
- iq["to"] = to_jid;
- iq["id"] = id;
- iq["type"] = "result";
- iq.close();
- this->send_stanza(iq);
-}
-
-void XmppComponent::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;
- item.close();
- query.add_child(std::move(item));
- }
- query.close();
- iq.add_child(std::move(query));
- iq.close();
- this->send_stanza(iq);
-}
-
-std::string XmppComponent::next_id()
-{
- char uuid_str[37];
- uuid_t uuid;
- uuid_generate(uuid);
- uuid_unparse(uuid, uuid_str);
- return uuid_str;
-}
diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp
deleted file mode 100644
index a0b06a6..0000000
--- a/src/xmpp/xmpp_component.hpp
+++ /dev/null
@@ -1,303 +0,0 @@
-#ifndef XMPP_COMPONENT_INCLUDED
-# define XMPP_COMPONENT_INCLUDED
-
-#include <xmpp/adhoc_commands_handler.hpp>
-#include <network/tcp_socket_handler.hpp>
-#include <xmpp/xmpp_parser.hpp>
-#include <bridge/bridge.hpp>
-
-#include <unordered_map>
-#include <memory>
-#include <string>
-#include <map>
-
-#define STREAM_NS "http://etherx.jabber.org/streams"
-#define COMPONENT_NS "jabber:component:accept"
-#define MUC_NS "http://jabber.org/protocol/muc"
-#define MUC_USER_NS MUC_NS"#user"
-#define MUC_ADMIN_NS MUC_NS"#admin"
-#define DISCO_NS "http://jabber.org/protocol/disco"
-#define DISCO_ITEMS_NS DISCO_NS"#items"
-#define DISCO_INFO_NS DISCO_NS"#info"
-#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im"
-#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas"
-#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams"
-#define VERSION_NS "jabber:iq:version"
-#define ADHOC_NS "http://jabber.org/protocol/commands"
-#define PING_NS "urn:xmpp:ping"
-
-class 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)>;
-
-/**
- * An XMPP component, communicating with an XMPP server using the protocole
- * described in XEP-0114: Jabber Component Protocol
- *
- * TODO: implement XEP-0225: Component Connections
- */
-class XmppComponent: public TCPSocketHandler
-{
-public:
- explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
- ~XmppComponent();
-
- void on_connection_failed(const std::string& reason) override final;
- void on_connected() override final;
- void on_connection_close(const std::string& error) override final;
- void parse_in_buffer(const size_t size) override final;
-
- /**
- * Returns the bridge for the given user. If it does not exist, return
- * nullptr.
- */
- Bridge* find_user_bridge(const std::string& user_jid);
- /**
- * Return a list of all the managed bridges.
- */
- std::list<Bridge*> get_bridges() const;
-
- /**
- * Returns a unique id, to be used in the 'id' element of our iq stanzas.
- */
- static std::string next_id();
- /**
- * 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();
- bool is_document_open() const;
- /**
- * 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();
- /**
- * Connect to the XMPP server.
- */
- void start();
- /**
- * Reset the component so we can use the component on a new XMPP stream
- */
- void reset();
- /**
- * Serialize the stanza and add it to the out_buf to be sent to the
- * server.
- */
- void send_stanza(const Stanza& stanza);
- /**
- * Handle the opening of the remote stream
- */
- void on_remote_stream_open(const XmlNode& node);
- /**
- * Handle the closing of the remote stream
- */
- void on_remote_stream_close(const XmlNode& node);
- /**
- * Handle received stanzas
- */
- void on_stanza(const Stanza& stanza);
- /**
- * Send an error stanza. Message being the name of the element inside the
- * stanza, and explanation being a short human-readable sentence
- * describing the error.
- */
- void send_stream_error(const std::string& message, const std::string& explanation);
- /**
- * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error
- */
- void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
- const std::string& id, const std::string& error_type,
- const std::string& defined_condition, const std::string& text,
- const bool fulljid=true);
- /**
- * Send the closing signal for our document (not closing the connection though).
- */
- void close_document();
- /**
- * Send a message from from@served_hostname, with the given body
- *
- * If fulljid is false, the provided 'from' doesn't contain the
- * server-part of the JID and must be added.
- */
- void send_message(const std::string& from, Xmpp::body&& body,
- const std::string& to, const std::string& type,
- const bool fulljid=false);
- /**
- * Send a join from a new participant
- */
- void send_user_join(const std::string& from,
- const std::string& nick,
- const std::string& realjid,
- const std::string& affiliation,
- const std::string& role,
- const std::string& to,
- const bool self);
- /**
- * Send an error to indicate that the user tried to join an invalid room
- */
- void send_invalid_room_error(const std::string& muc_jid,
- const std::string& nick,
- const std::string& to);
- /**
- * Send an error to indicate that the user tried to send a message to an
- * invalid user.
- */
- void send_invalid_user_error(const std::string& user_name,
- const std::string& to);
- /**
- * Send the MUC topic to the user
- */
- void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to);
- /**
- * Send a (non-private) message to the MUC
- */
- void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to);
- /**
- * Send an unavailable presence for this nick
- */
- void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self);
- /**
- * Indicate that a participant changed his nick
- */
- void send_nick_change(const std::string& muc_name,
- const std::string& old_nick,
- const std::string& new_nick,
- const std::string& affiliation,
- const std::string& role,
- const std::string& jid_to,
- const bool self);
- /**
- * An user is kicked from a room
- */
- void kick_user(const std::string& muc_name,
- const std::string& target,
- const std::string& reason,
- const std::string& author,
- const std::string& jid_to);
- /**
- * Send a generic presence error
- */
- void send_presence_error(const std::string& muc_name,
- const std::string& nickname,
- const std::string& jid_to,
- const std::string& type,
- const std::string& condition,
- const std::string& error_code,
- const std::string& text);
- /**
- * Send a presence from the MUC indicating a change in the role and/or
- * affiliation of a participant
- */
- void send_affiliation_role_change(const std::string& muc_name,
- const std::string& target,
- const std::string& affiliation,
- const std::string& role,
- const std::string& jid_to);
- /**
- * Send a result IQ with the gateway disco informations.
- */
- void send_self_disco_info(const std::string& id, const std::string& jid_to);
- /**
- * Send a result IQ with the given version, or the gateway version if the
- * passed string is empty.
- */
- void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
- const std::string& version="");
- /**
- * Send the list of all available ad-hoc commands to that JID. The list is
- * different depending on what JID made the request.
- */
- void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid);
- /**
- * 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 an empty iq of type result
- */
- void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from);
- /**
- * 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_handshake(const Stanza& stanza);
- void handle_presence(const Stanza& stanza);
- void handle_message(const Stanza& stanza);
- void handle_iq(const Stanza& stanza);
- void handle_error(const Stanza& stanza);
-
- /**
- * Whether or not we ever succeeded our authentication to the XMPP server
- */
- bool ever_auth;
- /**
- * Whether or not this is the first consecutive try on connecting to the
- * XMPP server. We use this to delay the connection attempt for a few
- * seconds, if it is not the first try.
- */
- bool first_connection_try;
-
-private:
- /**
- * Return the bridge associated with the given full JID. Create a new one
- * if none already exist.
- */
- Bridge* get_user_bridge(const std::string& user_jid);
- /**
- * Return a buffer provided by the XML parser, to read data directly into
- * it, and avoiding some unnecessary copy.
- */
- void* get_receive_buffer(const size_t size) const override final;
- XmppParser parser;
- std::string stream_id;
- std::string served_hostname;
- std::string secret;
- bool authenticated;
- /**
- * Whether or not OUR XMPP document is open
- */
- bool doc_open;
-
- std::unordered_map<std::string, std::function<void(const Stanza&)>> stanza_handlers;
- AdhocCommandsHandler adhoc_commands_handler;
-
- /**
- * 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 full
- * jid
- */
- std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges;
-
- XmppComponent(const XmppComponent&) = delete;
- XmppComponent(XmppComponent&&) = delete;
- XmppComponent& operator=(const XmppComponent&) = delete;
- XmppComponent& operator=(XmppComponent&&) = delete;
-};
-
-#endif // XMPP_COMPONENT_INCLUDED