Remove all the libs that are now in louloulibs

This commit is contained in:
Florent Le Coz
2015-02-27 12:26:48 +01:00
parent 1028d4d549
commit d600a2843f
51 changed files with 714 additions and 4917 deletions
+7 -3
View File
@@ -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);
-143
View File
@@ -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");
}
-69
View File
@@ -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
-37
View File
@@ -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;
}
-70
View File
@@ -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
+568
View File
@@ -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);
}
+111
View File
@@ -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
+1 -1
View File
@@ -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
File diff suppressed because it is too large Load Diff
-303
View File
@@ -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