diff options
Diffstat (limited to 'src/xmpp')
-rw-r--r-- | src/xmpp/adhoc_commands_handler.cpp | 2 | ||||
-rw-r--r-- | src/xmpp/biboumi_adhoc_commands.cpp | 71 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.cpp | 89 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.cpp | 22 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.hpp | 12 |
5 files changed, 142 insertions, 54 deletions
diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index bb48781..bc4c108 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -41,7 +41,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co XmlSubNode condition(error, STANZA_NS":item-not-found"); } else if (command_it->second.is_admin_only() && - Config::get("admin", "") != jid.local + "@" + jid.domain) + !Config::is_in_list("admin", jid.bare())) { XmlSubNode error(command_node, ADHOC_NS":error"); error["type"] = "cancel"; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index bcdac39..53806d6 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -14,6 +14,7 @@ #ifdef USE_DATABASE #include <database/database.hpp> +#include <database/save.hpp> #endif #ifndef HAS_PUT_TIME @@ -196,7 +197,7 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, options.col<Database::GlobalPersistent>() = to_bool(value->get_inner()); } - options.save(Database::db); + save(options, *Database::db); command_node.delete_all_children(); XmlSubNode note(command_node, "note"); @@ -219,6 +220,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com server_domain = target.local; auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, server_domain); + auto commands = Database::get_after_connection_commands(options); XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; @@ -228,6 +230,19 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com instructions.set_inner("Edit the form, to configure the settings of the IRC server " + server_domain); { + XmlSubNode field(x, "field"); + field["var"] = "address"; + field["type"] = "text-single"; + field["label"] = "Address"; + field["desc"] = "The address (hostname or IP) to connect to."; + XmlSubNode value(field, "value"); + if (options.col<Database::Address>().empty()) + value.set_inner(server_domain); + else + value.set_inner(options.col<Database::Address>()); + } + + { XmlSubNode ports(x, "field"); ports["var"] = "ports"; ports["type"] = "text-multi"; @@ -279,6 +294,20 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com } } #endif + + { + XmlSubNode field(x, "field"); + field["var"] = "nick"; + field["type"] = "text-single"; + field["label"] = "Nickname"; + field["desc"] = "If set, will override the nickname provided in the initial presence sent to join the first server channel"; + if (!options.col<Database::Nick>().empty()) + { + XmlSubNode value(field, "value"); + value.set_inner(options.col<Database::Nick>()); + } + } + { XmlSubNode pass(x, "field"); pass["var"] = "pass"; @@ -294,14 +323,14 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com { XmlSubNode after_cnt_cmd(x, "field"); - after_cnt_cmd["var"] = "after_connect_command"; - after_cnt_cmd["type"] = "text-single"; - after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server."; - after_cnt_cmd["label"] = "After-connection IRC command"; - if (!options.col<Database::AfterConnectionCommand>().empty()) + after_cnt_cmd["var"] = "after_connect_commands"; + after_cnt_cmd["type"] = "text-multi"; + after_cnt_cmd["desc"] = "Custom IRC commands sent after the connection is established with the server."; + after_cnt_cmd["label"] = "After-connection IRC commands"; + for (const auto& command: commands) { XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); - after_cnt_cmd_value.set_inner(options.col<Database::AfterConnectionCommand>()); + after_cnt_cmd_value.set_inner(command.col<Database::AfterConnectionCommand>()); } } @@ -371,10 +400,16 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com server_domain = target.local; auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, server_domain); + auto commands = Database::get_after_connection_commands(options); + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) { const XmlNode* value = field->get_child("value", "jabber:x:data"); const std::vector<const XmlNode*> values = field->get_children("value", "jabber:x:data"); + + if (field->get_tag("var") == "address" && value) + options.col<Database::Address>() = value->get_inner(); + if (field->get_tag("var") == "ports") { std::string ports; @@ -406,11 +441,22 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com #endif // BOTAN_FOUND + else if (field->get_tag("var") == "nick" && value) + options.col<Database::Nick>() = value->get_inner(); + else if (field->get_tag("var") == "pass" && value) options.col<Database::Pass>() = value->get_inner(); - else if (field->get_tag("var") == "after_connect_command" && value) - options.col<Database::AfterConnectionCommand>() = value->get_inner(); + else if (field->get_tag("var") == "after_connect_commands") + { + commands.clear(); + for (const auto& val: values) + { + auto command = Database::after_connection_commands.row(); + command.col<Database::AfterConnectionCommand>() = val->get_inner(); + commands.push_back(std::move(command)); + } + } else if (field->get_tag("var") == "username" && value) { @@ -431,7 +477,8 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com } Database::invalidate_encoding_in_cache(); - options.save(Database::db); + save(options, *Database::db); + Database::set_after_connection_commands(options, commands); command_node.delete_all_children(); XmlSubNode note(command_node, "note"); @@ -600,7 +647,7 @@ bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const } Database::invalidate_encoding_in_cache(requester.bare(), iid.get_server(), iid.get_local()); - options.save(Database::db); + save(options, *Database::db); } return true; } @@ -611,7 +658,7 @@ bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { const Jid owner(session.get_owner_jid()); - if (owner.bare() != Config::get("admin", "")) + if (!Config::is_in_list("admin", owner.bare())) { // A non-admin is not allowed to disconnect other users, only // him/herself, so we just skip this step auto next_step = session.get_next_step(); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 8775869..6dc5fc5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -7,6 +7,7 @@ #include <xmpp/adhoc_command.hpp> #include <xmpp/biboumi_adhoc_commands.hpp> #include <bridge/list_element.hpp> +#include <utils/encoding.hpp> #include <config/config.hpp> #include <utils/time.hpp> #include <xmpp/jid.hpp> @@ -147,13 +148,10 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) try { if (iid.type == Iid::Type::Channel && !iid.get_server().empty()) - { // presence toward a MUC that corresponds to an irc channel, or a - // dummy channel if iid.chan is empty + { // presence toward a MUC that corresponds to an irc channel 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, from.resource); const XmlNode* x = stanza.get_child("x", MUC_NS); const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; const XmlNode* history = x ? x->get_child("history", MUC_NS): nullptr; @@ -181,7 +179,9 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) history_limit.stanzas = 0; } bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "", - from.resource, history_limit); + from.resource, history_limit, x != nullptr); + if (!own_nick.empty() && own_nick != to.resource) + bridge->send_irc_nick_change(iid, to.resource, from.resource); } else if (type == "unavailable") { @@ -279,7 +279,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) { if (body && !body->get_inner().empty()) { - bridge->send_channel_message(iid, body->get_inner()); + bridge->send_channel_message(iid, body->get_inner(), id); } const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); if (subject) @@ -466,8 +466,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) #ifdef USE_DATABASE else if ((query = stanza.get_child("query", MAM_NS))) { - if (this->handle_mam_request(stanza)) - stanza_error.disable(); + try { + if (this->handle_mam_request(stanza)) + stanza_error.disable(); + } catch (const Database::RecordNotFound& exc) { + error_name = "item-not-found"; + return; + } } else if ((query = stanza.get_child("query", MUC_OWNER_NS))) { @@ -546,24 +551,21 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (to.local.empty()) { // Get biboumi's adhoc commands this->send_adhoc_commands_list(id, from, this->served_hostname, - (Config::get("admin", "") == - from_jid.bare()), + Config::is_in_list("admin", from_jid.bare()), this->adhoc_commands_handler); stanza_error.disable(); } else if (iid.type == Iid::Type::Server) { // Get the server's adhoc commands this->send_adhoc_commands_list(id, from, to_str, - (Config::get("admin", "") == - from_jid.bare()), + Config::is_in_list("admin", from_jid.bare()), this->irc_server_adhoc_commands_handler); stanza_error.disable(); } else if (iid.type == Iid::Type::Channel && to.resource.empty()) { // Get the channel's adhoc commands this->send_adhoc_commands_list(id, from, to_str, - (Config::get("admin", "") == - from_jid.bare()), + Config::is_in_list("admin", from_jid.bare()), this->irc_channel_adhoc_commands_handler); stanza_error.disable(); } @@ -714,29 +716,58 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) } const XmlNode* set = query->get_child("set", RSM_NS); int limit = -1; + Id::real_type reference_record_id{Id::unset_value}; + Database::Paging paging_order{Database::Paging::first}; if (set) { const XmlNode* max = set->get_child("max", RSM_NS); if (max) limit = std::atoi(max->get_inner().data()); + const XmlNode* after = set->get_child("after", RSM_NS); + if (after) + { + auto after_record = Database::get_muc_log(from.bare(), iid.get_local(), iid.get_server(), + after->get_inner(), start, end); + reference_record_id = after_record.col<Id>(); + } + const XmlNode* before = set->get_child("before", RSM_NS); + if (before) + { + paging_order = Database::Paging::last; + if (!before->get_inner().empty()) + { + auto before_record = Database::get_muc_log(from.bare(), iid.get_local(), iid.get_server(), before->get_inner(), start, end); + reference_record_id = before_record.col<Id>(); + } + } } - // If the archive is really big, and the client didn’t specify any - // limit, we avoid flooding it: we set an arbitrary max limit. - if (limit == -1 && start.empty() && end.empty()) + // Do not send more than 100 messages, even if the client asked for more, + // or if it didn’t specify any limit. + // 101 is just a trick to know if there are more available messages. + // If our query returns 101 message, we know it’s incomplete, but we + // still send only 100 + if ((limit == -1 && start.empty() && end.empty()) + || limit > 100) + limit = 101; + auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end, reference_record_id, paging_order); + bool complete = true; + if (lines.size() > 100) { - limit = 100; + complete = false; + lines.erase(lines.begin(), std::prev(lines.end(), 100)); } - const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); for (const Database::MucLogLine& line: lines) - { - if (!line.col<Database::Nick>().empty()) - this->send_archived_message(line, to.full(), from.full(), query_id); - } + { + if (!line.col<Database::Nick>().empty()) + this->send_archived_message(line, to.full(), from.full(), query_id); + } { auto fin_ptr = std::make_unique<XmlNode>("fin"); { XmlNode& fin = *(fin_ptr.get()); fin["xmlns"] = MAM_NS; + if (complete) + fin["complete"] = "true"; XmlSubNode set(fin, "set"); set["xmlns"] = RSM_NS; if (!lines.empty()) @@ -881,7 +912,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["category"] = "conference"; identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; - for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) + for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS}) { XmlSubNode feature(query, "feature"); feature["var"] = ns; @@ -905,7 +936,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s identity["category"] = "conference"; identity["type"] = "irc"; identity["name"] = "IRC server " + from.local + " over Biboumi"; - for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) + for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS}) { XmlSubNode feature(query, "feature"); feature["var"] = ns; @@ -947,8 +978,8 @@ void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const XmlSubNode identity(query, "identity"); identity["category"] = "conference"; identity["type"] = "irc"; - identity["name"] = "IRC channel " + iid.get_local() + " from server " + iid.get_server() + " over biboumi"; - for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) + identity["name"] = ""s + iid.get_local() + " on " + iid.get_server(); + for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS}) { XmlSubNode feature(query, "feature"); feature["var"] = ns; @@ -1010,7 +1041,9 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std for (auto it = begin; it != end; ++it) { XmlSubNode item(query, "item"); - item["jid"] = it->channel + "@" + this->served_hostname; + std::string channel_name = it->channel; + xep0106::encode(channel_name); + item["jid"] = channel_name + "@" + this->served_hostname; } if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 8f6826e..b3d925e 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -2,6 +2,7 @@ #include <utils/scopeguard.hpp> #include <utils/tolower.hpp> #include <logger/logger.hpp> +#include <utils/uuid.hpp> #include <xmpp/xmpp_component.hpp> #include <config/config.hpp> @@ -14,8 +15,6 @@ #include <iostream> #include <set> -#include <uuid/uuid.h> - #include <cstdlib> #include <set> @@ -364,10 +363,11 @@ void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, cons 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, std::string uuid) +void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to, std::string uuid, std::string id) { Stanza message("message"); message["to"] = jid_to; + message["id"] = std::move(id); if (!nick.empty()) message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; else // Message from the room itself @@ -398,7 +398,8 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str this->send_stanza(message); } -void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, std::time_t timestamp) +#ifdef USE_DATABASE +void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, Database::time_point::rep timestamp) { Stanza message("message"); message["to"] = jid_to; @@ -421,9 +422,11 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: this->send_stanza(message); } +#endif void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, - const std::string& jid_to, const bool self, const bool user_requested) + const std::string& jid_to, const bool self, const bool user_requested, + const std::string& affiliation, const std::string& role) { Stanza presence("presence"); { @@ -445,6 +448,9 @@ void XmppComponent::send_muc_leave(const std::string& muc_name, const std::strin status["code"] = "332"; } } + XmlSubNode item(x, "item"); + item["affiliation"] = affiliation; + item["role"] = role; if (!message_str.empty()) { XmlSubNode status(presence, "status"); @@ -667,9 +673,5 @@ void XmppComponent::send_iq_result(const std::string& id, const std::string& to_ std::string XmppComponent::next_id() { - char uuid_str[37]; - uuid_t uuid; - uuid_generate(uuid); - uuid_unparse(uuid, uuid_str); - return uuid_str; + return utils::gen_uuid(); } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 3950863..e18da40 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -1,8 +1,10 @@ #pragma once +#include "biboumi.h" #include <xmpp/adhoc_commands_handler.hpp> #include <network/tcp_client_socket_handler.hpp> +#include <database/database.hpp> #include <xmpp/xmpp_parser.hpp> #include <xmpp/body.hpp> @@ -35,6 +37,7 @@ #define RSM_NS "http://jabber.org/protocol/rsm" #define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic" #define STABLE_ID_NS "urn:xmpp:sid:0" +#define STABLE_MUC_ID_NS "http://jabber.org/protocol/muc#stable_id" /** * An XMPP component, communicating with an XMPP server using the protocole @@ -132,12 +135,14 @@ public: * 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, - std::string uuid); + std::string uuid, std::string id); +#ifdef USE_DATABASE /** * Send a message, with a <delay/> element, part of a MUC history */ void send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body, - const std::string& jid_to, const std::time_t timestamp); + const std::string& jid_to, Database::time_point::rep timestamp); +#endif /** * Send an unavailable presence for this nick */ @@ -146,7 +151,8 @@ public: Xmpp::body&& message, const std::string& jid_to, const bool self, - const bool user_requested); + const bool user_requested, + const std::string& affiliation, const std::string& role); /** * Indicate that a participant changed his nick */ |