diff options
Diffstat (limited to 'src/xmpp')
-rw-r--r-- | src/xmpp/adhoc_command.cpp | 10 | ||||
-rw-r--r-- | src/xmpp/adhoc_commands_handler.cpp | 143 | ||||
-rw-r--r-- | src/xmpp/adhoc_commands_handler.hpp | 69 | ||||
-rw-r--r-- | src/xmpp/adhoc_session.cpp | 37 | ||||
-rw-r--r-- | src/xmpp/adhoc_session.hpp | 70 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.cpp | 568 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.hpp | 111 | ||||
-rw-r--r-- | src/xmpp/jid.cpp | 2 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.cpp | 1194 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.hpp | 303 |
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 |