summaryrefslogtreecommitdiff
path: root/louloulibs/xmpp/xmpp_component.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'louloulibs/xmpp/xmpp_component.cpp')
-rw-r--r--louloulibs/xmpp/xmpp_component.cpp664
1 files changed, 664 insertions, 0 deletions
diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp
new file mode 100644
index 0000000..e87cdf7
--- /dev/null
+++ b/louloulibs/xmpp/xmpp_component.cpp
@@ -0,0 +1,664 @@
+#include <utils/timed_events.hpp>
+#include <utils/scopeguard.hpp>
+#include <utils/tolower.hpp>
+#include <logger/logger.hpp>
+
+#include <xmpp/xmpp_component.hpp>
+#include <config/config.hpp>
+#include <xmpp/jid.hpp>
+#include <utils/sha1.hpp>
+
+#include <stdexcept>
+#include <iostream>
+#include <set>
+
+#include <stdio.h>
+
+#include <uuid.h>
+
+#include <louloulibs.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),
+ secret(secret),
+ authenticated(false),
+ doc_open(false),
+ served_hostname(hostname),
+ stanza_handlers{},
+ 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("error",
+ std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
+}
+
+void XmppComponent::start()
+{
+ this->connect(Config::get("xmpp_server_ip", "127.0.0.1"), 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;
+ auto data = "<stream:stream to='"s + this->served_hostname + \
+ "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='" COMPONENT_NS "'>";
+ log_debug("XMPP SENDING: ", data);
+ this->send_data(std::move(data));
+ 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::on_remote_stream_open(const XmlNode& node)
+{
+ log_debug("XMPP RECEIVING: ", 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';
+
+ auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + digest + "</handshake>";
+ log_debug("XMPP SENDING: ", data);
+ this->send_data(std::move(data));
+}
+
+void XmppComponent::on_remote_stream_close(const XmlNode& node)
+{
+ log_debug("XMPP RECEIVING: ", 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);
+ node.add_child(std::move(error));
+ 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;
+ error.add_child(std::move(inner_error));
+ if (!text.empty())
+ {
+ XmlNode text_node("text");
+ text_node["xmlns"] = STANZA_NS;
+ text_node.set_inner(text);
+ error.add_child(std::move(text_node));
+ }
+ node.add_child(std::move(error));
+ 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
+ this->after_handshake();
+}
+
+void XmppComponent::handle_error(const Stanza& stanza)
+{
+ const 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
+
+}
+
+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));
+ 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::move(std::get<1>(body)));
+ node.add_child(std::move(html));
+ }
+ 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;
+ }
+ x.add_child(std::move(item));
+
+ if (self)
+ {
+ XmlNode status("status");
+ status["code"] = "110";
+ x.add_child(std::move(status));
+ }
+ node.add_child(std::move(x));
+ 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;
+ 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;
+ 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);
+ error.add_child(std::move(text));
+ presence.add_child(std::move(error));
+ 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;
+ 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;
+ 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);
+ error.add_child(std::move(text));
+ message.add_child(std::move(error));
+ this->send_stanza(message);
+}
+
+void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who)
+{
+ XmlNode message("message");
+ message["to"] = to;
+ if (who.empty())
+ message["from"] = from + "@" + this->served_hostname;
+ else
+ message["from"] = from + "@" + this->served_hostname + "/" + who;
+ message["type"] = "groupchat";
+ XmlNode subject("subject");
+ subject.set_inner(std::get<0>(topic));
+ message.add_child(std::move(subject));
+ 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));
+ 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::move(std::get<1>(xmpp_body)));
+ message.add_child(std::move(html));
+ }
+ 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";
+ x.add_child(std::move(status));
+ }
+ presence.add_child(std::move(x));
+ if (!message_str.empty())
+ {
+ XmlNode status("status");
+ status.set_inner(message_str);
+ presence.add_child(std::move(status));
+ }
+ 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;
+ x.add_child(std::move(item));
+ XmlNode status("status");
+ status["code"] = "303";
+ x.add_child(std::move(status));
+ if (self)
+ {
+ XmlNode status2("status");
+ status2["code"] = "110";
+ x.add_child(std::move(status2));
+ }
+ presence.add_child(std::move(x));
+ 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
+ item.add_child(std::move(actor));
+ XmlNode reason("reason");
+ reason.set_inner(txt);
+ item.add_child(std::move(reason));
+ x.add_child(std::move(item));
+ XmlNode status("status");
+ status["code"] = "307";
+ x.add_child(std::move(status));
+ presence.add_child(std::move(x));
+ 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;
+ 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;
+ error.add_child(std::move(subnode));
+ presence.add_child(std::move(error));
+ 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;
+ x.add_child(std::move(item));
+ presence.add_child(std::move(x));
+ this->send_stanza(presence);
+}
+
+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");
+ query.add_child(std::move(name));
+ XmlNode version("version");
+ version.set_inner(SOFTWARE_VERSION);
+ query.add_child(std::move(version));
+ XmlNode os("os");
+ os.set_inner(SYSTEM_NAME);
+ query.add_child(std::move(os));
+ }
+ else
+ {
+ XmlNode name("name");
+ name.set_inner(version);
+ query.add_child(std::move(name));
+ }
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
+
+void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid,
+ const std::string& from_jid,
+ const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler)
+{
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["to"] = requester_jid;
+ iq["from"] = from_jid;
+ XmlNode query("query");
+ query["xmlns"] = DISCO_ITEMS_NS;
+ query["node"] = ADHOC_NS;
+ for (const auto& kv: adhoc_handler.get_commands())
+ {
+ if (kv.second.is_admin_only() && !with_admin_only)
+ continue;
+ XmlNode item("item");
+ item["jid"] = from_jid;
+ item["node"] = kv.first;
+ item["name"] = kv.second.name;
+ query.add_child(std::move(item));
+ }
+ iq.add_child(std::move(query));
+ 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;
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
+
+void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid)
+{
+ Stanza iq("iq");
+ iq["from"] = from_full_jid;
+ iq["to"] = to_jid;
+ iq["id"] = id;
+ iq["type"] = "result";
+ this->send_stanza(iq);
+}
+
+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";
+ 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;
+}