From 0c8adc85f7373a85de8b3edc6cac87d5f7389bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 11 Nov 2016 02:54:48 +0100 Subject: Move all the connect() logic from TCPSocketHandler into a subclass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way, TCPSocketHandler only deal with the message sending/receiving, not the connect() or anything else. This will be used for implementing servers (because when a client is accepted, we don’t need all the connect() and dns resolution stuff). --- src/irc/irc_client.cpp | 4 ++-- src/irc/irc_client.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index b0d3a47..b13e5ab 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -131,7 +131,7 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname const std::string& nickname, const std::string& username, const std::string& realname, const std::string& user_hostname, Bridge& bridge): - TCPSocketHandler(poller), + TCPClientSocketHandler(poller), hostname(hostname), user_hostname(user_hostname), username(username), @@ -338,7 +338,7 @@ void IrcClient::parse_in_buffer(const size_t) if (pos == std::string::npos) break ; IrcMessage message(this->in_buf.substr(0, pos)); - this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); + this->consume_in_buffer(pos + 2); log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message); // Call the standard callback (if any), associated with the command diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 1b4d892..4edc32c 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -23,7 +23,7 @@ class Bridge; * Represent one IRC client, i.e. an endpoint connected to a single IRC * server, through a TCP socket, receiving and sending commands to it. */ -class IrcClient: public TCPSocketHandler +class IrcClient: public TCPClientSocketHandler { public: explicit IrcClient(std::shared_ptr poller, const std::string& hostname, -- cgit v1.2.3 From d872c2b49214c0a4db40a9e2d860802d9eedc563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 15 Nov 2016 00:23:19 +0100 Subject: Support the ident protocol fix #3211 --- src/bridge/bridge.cpp | 5 +++ src/bridge/bridge.hpp | 1 + src/identd/identd_server.hpp | 38 +++++++++++++++++++++++ src/identd/identd_socket.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++ src/identd/identd_socket.hpp | 44 +++++++++++++++++++++++++++ src/main.cpp | 43 ++++++++++++++++---------- src/xmpp/biboumi_component.cpp | 1 - 7 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 src/identd/identd_server.hpp create mode 100644 src/identd/identd_socket.cpp create mode 100644 src/identd/identd_socket.hpp (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a0ecc6e..6fb03bd 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1033,6 +1033,11 @@ std::unordered_map>& Bridge::get_irc_cli return this->irc_clients; } +const std::unordered_map>& Bridge::get_irc_clients() const +{ + return this->irc_clients; +} + std::set Bridge::get_chantypes(const std::string& hostname) const { IrcClient* irc = this->find_irc_client(hostname); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 18ebfeb..8f2dcef 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -231,6 +231,7 @@ public: */ void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); + const std::unordered_map>& get_irc_clients() const; std::set get_chantypes(const std::string& hostname) const; #ifdef USE_DATABASE void set_record_history(const bool val); diff --git a/src/identd/identd_server.hpp b/src/identd/identd_server.hpp new file mode 100644 index 0000000..4270749 --- /dev/null +++ b/src/identd/identd_server.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +class BiboumiComponent; + +class IdentdServer: public TcpSocketServer +{ + public: + IdentdServer(const BiboumiComponent& biboumi_component, std::shared_ptr poller, const uint16_t port): + TcpSocketServer(poller, port), + biboumi_component(biboumi_component) + {} + + const BiboumiComponent& get_biboumi_component() const + { + return this->biboumi_component; + } + void shutdown() + { + if (this->poller->is_managing_socket(this->socket)) + this->poller->remove_socket_handler(this->socket); + ::close(this->socket); + } + void clean() + { + this->sockets.erase(std::remove_if(this->sockets.begin(), this->sockets.end(), + [](const std::unique_ptr& socket) + { + return socket->get_socket() == -1; + }), + this->sockets.end()); + } + private: + const BiboumiComponent& biboumi_component; +}; diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp new file mode 100644 index 0000000..c1ad55e --- /dev/null +++ b/src/identd/identd_socket.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include + +#include + +IdentdSocket::IdentdSocket(std::shared_ptr poller, const socket_t socket, TcpSocketServer& server): + TCPSocketHandler(poller), + server(dynamic_cast(server)) +{ + this->socket = socket; +} + +void IdentdSocket::parse_in_buffer(const std::size_t) +{ + while (true) + { + const auto line_end = this->in_buf.find('\n'); + if (line_end == std::string::npos) + break; + std::istringstream line(this->in_buf.substr(0, line_end)); + this->consume_in_buffer(line_end + 1); + + uint16_t local_port; + uint16_t remote_port; + char sep; + line >> local_port >> sep >> remote_port; + const auto& xmpp = this->server.get_biboumi_component(); + auto response = this->generate_answer(xmpp, local_port, remote_port); + + this->send_data(std::move(response)); + } +} + +static std::string hash_jid(const std::string& jid) +{ + sha1nfo sha1; + sha1_init(&sha1); + sha1_write(&sha1, jid.data(), jid.size()); + const uint8_t* res = sha1_result(&sha1); + std::ostringstream result; + for (int i = 0; i < HASH_LENGTH; i++) + result << std::hex << std::setfill('0') << std::setw(2) << static_cast(res[i]); + return result.str(); +} + +std::string IdentdSocket::generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote) +{ + for (const Bridge* bridge: biboumi.get_bridges()) + { + for (const auto& pair: bridge->get_irc_clients()) + { + if (pair.second->match_port_pairt(local, remote)) + { + std::ostringstream os; + os << local << " , " << remote << " : USERID : OTHER : " << hash_jid(bridge->get_bare_jid()); + log_debug("Identd, sending: ", os.str()); + return os.str(); + } + } + } + std::ostringstream os; + os << local << " , " << remote << " ERROR : NO-USER"; + log_debug("Identd, sending: ", os.str()); + return os.str(); +} diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp new file mode 100644 index 0000000..6d6cc1d --- /dev/null +++ b/src/identd/identd_socket.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +class XmppComponent; +class IdentdSocket; +class IdentdServer; +template +class TcpSocketServer; + +class IdentdSocket: public TCPSocketHandler +{ + public: + IdentdSocket(std::shared_ptr poller, const socket_t socket, TcpSocketServer& server); + ~IdentdSocket() = default; + std::string generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote); + + void parse_in_buffer(const std::size_t size) override final; + void on_connection_close(const std::string& message) override final + {} + + bool is_connected() const override final + { + return true; + } + bool is_connecting() const override final + { + return false; + } + + private: + void connect() override + { assert(false); } + void on_connection_failed(const std::string&) override final + { assert(false); } + + IdentdServer& server; +}; diff --git a/src/main.cpp b/src/main.cpp index 019dff0..072345f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,8 @@ #include #include +#include + // A flag set by the SIGINT signal handler. static std::atomic stop(false); // Flag set by the SIGUSR1/2 signal handler. @@ -121,10 +123,13 @@ int main(int ac, char** av) sigaction(SIGUSR2, &on_sigusr, nullptr); auto p = std::make_shared(); + auto xmpp_component = std::make_shared(p, hostname, password); xmpp_component->start(); + IdentdServer identd(*xmpp_component, p, static_cast(Config::get_int("identd_port", 113))); + #ifdef CARES_FOUND DNSHandler::instance.watch_dns_sockets(p); #endif @@ -135,6 +140,7 @@ int main(int ac, char** av) // Check for empty irc_clients (not connected, or with no joined // channel) and remove them xmpp_component->clean(); + identd.clean(); if (stop) { log_info("Signal received, exiting..."); @@ -144,6 +150,7 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); + identd.shutdown(); // Cancel the timer for a potential reconnection TimedEventsManager::instance().cancel("XMPP reconnection"); } @@ -157,26 +164,30 @@ int main(int ac, char** av) // happened because we sent something invalid to it and it decided to // close the connection. This is a bug that should be fixed, but we // still reconnect automatically instead of dropping everything - if (!exiting && xmpp_component->ever_auth && + if (!exiting && !xmpp_component->is_connected() && !xmpp_component->is_connecting()) { - if (xmpp_component->first_connection_try == true) - { // immediately re-try to connect - xmpp_component->reset(); - xmpp_component->start(); - } - else - { // Re-connecting failed, we now try only each few seconds - auto reconnect_later = [xmpp_component]() + if (xmpp_component->ever_auth) { - xmpp_component->reset(); - xmpp_component->start(); - }; - TimedEvent event(std::chrono::steady_clock::now() + 2s, - reconnect_later, "XMPP reconnection"); - TimedEventsManager::instance().add_event(std::move(event)); - } + if (xmpp_component->first_connection_try == true) + { // immediately re-try to connect + xmpp_component->reset(); + xmpp_component->start(); + } + else + { // Re-connecting failed, we now try only each few seconds + auto reconnect_later = [xmpp_component]() + { + xmpp_component->reset(); + xmpp_component->start(); + }; + TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, "XMPP reconnection"); + TimedEventsManager::instance().add_event(std::move(event)); + } + } + else + identd.shutdown(); } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index d6782e2..ca24f3a 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3 From 4ab3cd653f3eb19ff82838f777984c9d16e64a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 15 Nov 2016 00:49:55 +0100 Subject: Fix some little compilation errors with some configs, from last commit --- src/identd/identd_server.hpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/identd/identd_server.hpp b/src/identd/identd_server.hpp index 4270749..5f74976 100644 --- a/src/identd/identd_server.hpp +++ b/src/identd/identd_server.hpp @@ -2,6 +2,7 @@ #include #include +#include #include class BiboumiComponent; -- cgit v1.2.3 From b5a7c66bea8f0bced4bb4ce5c52f17bc30317ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 16 Nov 2016 20:30:16 +0100 Subject: =?UTF-8?q?fix=20an=20=E2=80=9Cunused=20parameter=E2=80=9D=20warni?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/identd/identd_socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp index 6d6cc1d..98077f2 100644 --- a/src/identd/identd_socket.hpp +++ b/src/identd/identd_socket.hpp @@ -22,7 +22,7 @@ class IdentdSocket: public TCPSocketHandler std::string generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote); void parse_in_buffer(const std::size_t size) override final; - void on_connection_close(const std::string& message) override final + void on_connection_close(const std::string&) override final {} bool is_connected() const override final -- cgit v1.2.3 From ad22be41719e29b075d707eb964fee1c4c30cf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 25 Nov 2016 09:21:27 +0100 Subject: Do not fail to build if litesql is not there --- src/main.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 072345f..060372b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,7 +12,9 @@ #include #include -#include +#ifdef USE_DATABASE +# include +#endif #include @@ -85,11 +87,14 @@ int main(int ac, char** av) if (hostname.empty()) return config_help("hostname"); + +#ifdef USE_DATABASE try { - open_database(); - } catch (const litesql::DatabaseError&) { - return 1; - } + open_database(); + } catch (const litesql::DatabaseError&) { + return 1; + } +#endif // Block the signals we want to manage. They will be unblocked only during // the epoll_pwait or ppoll calls. This avoids some race conditions, -- cgit v1.2.3 From 732f53d798c86558e1e625c22e957243bb2d6467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 18 Nov 2016 20:54:41 +0100 Subject: Clean a few more things --- src/identd/identd_socket.hpp | 7 ------- 1 file changed, 7 deletions(-) (limited to 'src') diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp index 98077f2..1c2bd27 100644 --- a/src/identd/identd_socket.hpp +++ b/src/identd/identd_socket.hpp @@ -22,8 +22,6 @@ class IdentdSocket: public TCPSocketHandler std::string generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote); void parse_in_buffer(const std::size_t size) override final; - void on_connection_close(const std::string&) override final - {} bool is_connected() const override final { @@ -35,10 +33,5 @@ class IdentdSocket: public TCPSocketHandler } private: - void connect() override - { assert(false); } - void on_connection_failed(const std::string&) override final - { assert(false); } - IdentdServer& server; }; -- cgit v1.2.3 From eb8f1cbc07aee0c22daa2ff4c24362c244327867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Dec 2016 23:06:59 +0100 Subject: Avoid a potential nullptr dereference --- src/xmpp/biboumi_component.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ca24f3a..262596c 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -569,13 +569,11 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) Jid to(stanza.get_tag("to")); const XmlNode* query = stanza.get_child("query", MAM_NS); - std::string query_id; - if (query) - query_id = query->get_tag("queryid"); Iid iid(to.local, {'#', '&'}); - if (iid.type == Iid::Type::Channel && to.resource.empty()) + if (query && iid.type == Iid::Type::Channel && to.resource.empty()) { + const std::string query_id = query->get_tag("queryid"); std::string start; std::string end; const XmlNode* x = query->get_child("x", DATAFORM_NS); -- cgit v1.2.3 From 75778f418751bf06687ccc99454683bb43a9daf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 7 Dec 2016 18:28:29 +0100 Subject: Add some missing includes --- src/identd/identd_socket.cpp | 1 + src/xmpp/biboumi_adhoc_commands.cpp | 1 + 2 files changed, 2 insertions(+) (limited to 'src') diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp index c1ad55e..46553ca 100644 --- a/src/identd/identd_socket.cpp +++ b/src/identd/identd_socket.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 003b901..309cd82 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 379fe026fb79a8c5523d157bbfe22d30c7fee0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 10 Dec 2016 04:15:49 +0100 Subject: Properly convert the data into a number of seconds by using duration_cast --- src/database/database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/database/database.cpp b/src/database/database.cpp index f7d309b..cb0c78f 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -130,7 +130,7 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.owner = owner; line.ircChanName = iid.get_local(); line.ircServerName = iid.get_server(); - line.date = date.time_since_epoch().count() / 1'000'000'000; + line.date = std::chrono::duration_cast(date.time_since_epoch()).count(); line.body = body; line.nick = nick; -- cgit v1.2.3 From f653906f9de8cbcecf5717e18c696d1029fc2c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 11 Dec 2016 16:34:38 +0100 Subject: Add a None type for the Iid class (when the iid is completely empty) --- src/irc/iid.cpp | 5 ++++- src/irc/iid.hpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index d442013..6b07793 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -34,9 +34,10 @@ Iid::Iid(const std::string& iid, const Bridge *bridge) void Iid::set_type(const std::set& chantypes) { + if (this->local.empty() && this->server.empty()) + this->type = Iid::Type::None; if (this->local.empty()) return; - if (chantypes.count(this->local[0]) == 1) this->type = Iid::Type::Channel; else @@ -105,6 +106,8 @@ namespace std { { if (iid.type == Iid::Type::Server) return iid.get_server(); + else if (iid.get_local().empty() && iid.get_server().empty()) + return {}; else return iid.get_encoded_local() + iid.separator + iid.get_server(); } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 44861c1..81cf3ca 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -53,6 +53,7 @@ public: Channel, User, Server, + None, }; static constexpr char separator[]{"%"}; Iid(const std::string& iid, const std::set& chantypes); -- cgit v1.2.3 From eca31ce8db104f17ac74fd74aa9d7ef7e8f1470a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 11 Dec 2016 17:05:52 +0100 Subject: Do not change the nick when joining a second room with a different nick As the doc says, the nick changes must be explicit in an already joined room, and not when joining a room. --- src/bridge/bridge.cpp | 6 +++++- src/bridge/bridge.hpp | 2 +- src/xmpp/biboumi_component.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6fb03bd..1841b95 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -393,8 +393,12 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con } } -void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) +void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource) { + // We don’t change the nick if the presence was sent to a channel the resource is not in. + auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), iid.get_server()}, requesting_resource); + if (!res_in_chan) + return; IrcClient* irc = this->get_irc_client(iid.get_server()); irc->send_nick_command(new_nick); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 8f2dcef..e92747d 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -80,7 +80,7 @@ public: void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void send_raw_message(const std::string& hostname, const std::string& body); void leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource); - void send_irc_nick_change(const Iid& iid, const std::string& new_nick); + void send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid); void set_channel_topic(const Iid& iid, const std::string& subject); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 262596c..d646656 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -149,7 +149,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) { 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); + 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; bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "", -- cgit v1.2.3 From c65ae4754921fe1f9888afc30d26ed11d5275258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 12 Dec 2016 00:36:59 +0100 Subject: Auto accept presence subscription --- src/xmpp/biboumi_component.cpp | 44 ++++++++++++++++++++++++++++-------------- src/xmpp/biboumi_component.hpp | 7 ++----- 2 files changed, 32 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index d646656..f8d2f1d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -161,6 +161,16 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource); } } + else if (iid.type == Iid::Type::Server || iid.type == Iid::Type::None) + { + if (type == "subscribe") + { // Auto-accept any subscription request for an IRC server + this->add_to_roster(to_str, from.bare()); + this->accept_subscription(to_str, from.bare()); + this->ask_subscription(to_str, from.bare()); + } + + } else { // A user wants to join an invalid IRC channel, return a presence error to him/her @@ -751,20 +761,6 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, c } -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; - iq.add_child(std::move(query)); - this->send_stanza(iq); -} - void BiboumiComponent::send_ping_request(const std::string& from, const std::string& jid_to, const std::string& id) @@ -863,3 +859,23 @@ void BiboumiComponent::send_invitation(const std::string& room_target, message.add_child(std::move(x)); this->send_stanza(message); } + +void BiboumiComponent::accept_subscription(const std::string& from, const std::string& to) +{ + Stanza presence("presence"); + presence["from"] = from; + presence["to"] = to; + presence["id"] = this->next_id(); + presence["type"] = "subscribed"; + this->send_stanza(presence); +} + +void BiboumiComponent::ask_subscription(const std::string& from, const std::string& to) +{ + Stanza presence("presence"); + presence["from"] = from; + presence["to"] = to; + presence["id"] = this->next_id(); + presence["type"] = "subscribe"; + this->send_stanza(presence); +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 999001f..7cafdec 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -70,11 +70,6 @@ public: * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic */ void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to); - /** - * Send an iq version request - */ - void send_iq_version_request(const std::string& from, - const std::string& jid_to); /** * Send a ping request */ @@ -88,6 +83,8 @@ public: const ChannelList& channel_list, std::vector::const_iterator begin, std::vector::const_iterator end, const ResultSetInfo& rs_info); void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); + void accept_subscription(const std::string& from, const std::string& to); + void ask_subscription(const std::string& from, const std::string& to); /** * Handle the various stanza types */ -- cgit v1.2.3 From 59a73c93ec48e9fe1171956f08a59dd85b90d5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 12 Dec 2016 00:39:31 +0100 Subject: =?UTF-8?q?Do=20not=20send=20an=20=E2=80=9Cnot-implemented?= =?UTF-8?q?=E2=80=9D=20error=20when=20receiving=20an=20iq=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/biboumi_component.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f8d2f1d..246d828 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -289,7 +289,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } // We MUST return an iq, whatever happens, except if the type is -// "result". +// "result" or "error". // 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 @@ -557,6 +557,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } } } + else if (type == "error") + { + stanza_error.disable(); + } } catch (const IRCNotConnected& ex) { -- cgit v1.2.3 From 35e1155232ecc3e1cb526d07db3420b11f4717dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 12 Dec 2016 00:43:39 +0100 Subject: Remove a line from a WIP that should not have been added --- src/xmpp/biboumi_component.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 246d828..bd05bdc 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -165,7 +165,6 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) { if (type == "subscribe") { // Auto-accept any subscription request for an IRC server - this->add_to_roster(to_str, from.bare()); this->accept_subscription(to_str, from.bare()); this->ask_subscription(to_str, from.bare()); } -- cgit v1.2.3 From 4792c70635cd73d581861a6da9794655a5e72099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 13 Dec 2016 14:48:41 +0100 Subject: Send a presence error from the room, when receiving command ERR_BADCHANNELKEY fix #2886 --- src/irc/irc_client.cpp | 14 +++++++++++++- src/irc/irc_client.hpp | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index b13e5ab..ba13c6a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -66,6 +66,7 @@ static const std::unordered_mapon_user_mode(message); } +void IrcClient::on_channel_bad_key(const IrcMessage& message) +{ + this->on_generic_error(message); + const std::string& nickname = message.arguments[0]; + const std::string& channel = message.arguments[1]; + std::string text; + if (message.arguments.size() > 2) + text = message.arguments[2]; + + this->bridge.send_presence_error({channel, this->hostname, Iid::Type::Channel}, nickname, "auth", "not-authorized", "", text); +} + void IrcClient::on_channel_mode(const IrcMessage& message) { // For now, just transmit the modes so the user can know what happens diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4edc32c..4b942ad 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -257,6 +257,7 @@ public: void on_nick(const IrcMessage& message); void on_kick(const IrcMessage& message); void on_mode(const IrcMessage& message); + void on_channel_bad_key(const IrcMessage& message); /** * A mode towards our own user is received (note, that is different from a * channel mode towards or own nick, see -- cgit v1.2.3 From 5a5bb7f63222189ea0dcfbd387d5e34458ccefe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 14 Dec 2016 18:28:44 +0100 Subject: Introduce a XmlSubNode class that automatically adds itself into its parent --- src/xmpp/biboumi_adhoc_commands.cpp | 253 +++++++++++++----------------------- src/xmpp/biboumi_component.cpp | 227 +++++++++++++++----------------- 2 files changed, 194 insertions(+), 286 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 309cd82..f6f3cd1 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -26,40 +26,31 @@ void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& { auto& biboumi_component = static_cast(xmpp_component); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Disconnect a user from the gateway"); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Choose a user JID and a quit message"); - x.add_child(std::move(instructions)); - XmlNode jids_field("field"); + XmlSubNode jids_field(x, "field"); jids_field["var"] = "jids"; jids_field["type"] = "list-multi"; jids_field["label"] = "The JIDs to disconnect"; - XmlNode required("required"); - jids_field.add_child(std::move(required)); + XmlSubNode required(jids_field, "required"); for (Bridge* bridge: biboumi_component.get_bridges()) { - XmlNode option("option"); + XmlSubNode option(jids_field, "option"); option["label"] = bridge->get_jid(); - XmlNode value("value"); + XmlSubNode value(option, "value"); value.set_inner(bridge->get_jid()); - option.add_child(std::move(value)); - jids_field.add_child(std::move(option)); } - x.add_child(std::move(jids_field)); - XmlNode message_field("field"); + XmlSubNode message_field(x, "field"); message_field["var"] = "quit-message"; message_field["type"] = "text-single"; message_field["label"] = "Quit message"; - XmlNode message_value("value"); + XmlSubNode message_value(message_field, "value"); message_value.set_inner("Disconnected by admin"); - message_field.add_child(std::move(message_value)); - x.add_child(std::move(message_field)); - command_node.add_child(std::move(x)); } void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) @@ -98,7 +89,7 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X } command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; if (num == 0) note.set_inner("No user were disconnected."); @@ -106,15 +97,12 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X note.set_inner("1 user has been disconnected."); else note.set_inner(std::to_string(num) + " users have been disconnected."); - command_node.add_child(std::move(note)); return; } } - XmlNode error(ADHOC_NS":error"); + XmlSubNode error(command_node, ADHOC_NS":error"); error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - error.add_child(std::move(condition)); - command_node.add_child(std::move(error)); + XmlSubNode condition(error, STANZA_NS":bad-request"); session.terminate(); } @@ -127,43 +115,38 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman auto options = Database::get_global_options(owner.bare()); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Configure some global default settings."); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure your global settings for the component."); - x.add_child(std::move(instructions)); - - XmlNode required("required"); - XmlNode max_histo_length("field"); + XmlSubNode max_histo_length(x, "field"); max_histo_length["var"] = "max_history_length"; max_histo_length["type"] = "text-single"; max_histo_length["label"] = "Max history length"; max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; - XmlNode value("value"); - value.set_inner(std::to_string(options.maxHistoryLength.value())); - max_histo_length.add_child(std::move(value)); - x.add_child(std::move(max_histo_length)); + { + XmlSubNode value(max_histo_length, "value"); + value.set_inner(std::to_string(options.maxHistoryLength.value())); + } - XmlNode record_history("field"); + XmlSubNode record_history(x, "field"); record_history["var"] = "record_history"; record_history["type"] = "boolean"; record_history["label"] = "Record history"; record_history["desc"] = "Whether to save the messages into the database, or not"; - value.set_name("value"); - if (options.recordHistory.value()) - value.set_inner("true"); - else - value.set_inner("false"); - record_history.add_child(std::move(value)); - x.add_child(std::move(record_history)); - - command_node.add_child(std::move(x)); + { + XmlSubNode value(record_history, "value"); + value.set_name("value"); + if (options.recordHistory.value()) + value.set_inner("true"); + else + value.set_inner("false"); + } } void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) @@ -195,17 +178,14 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, options.update(); command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; note.set_inner("Configuration successfully applied."); - command_node.add_child(std::move(note)); return; } - XmlNode error(ADHOC_NS":error"); + XmlSubNode error(command_node, ADHOC_NS":error"); error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - error.add_child(std::move(condition)); - command_node.add_child(std::move(error)); + XmlSubNode condition(error, STANZA_NS":bad-request"); session.terminate(); } @@ -219,18 +199,16 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, server_domain); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Configure the IRC server "s + server_domain); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain); - x.add_child(std::move(instructions)); XmlNode required("required"); - XmlNode ports("field"); + XmlSubNode ports(x, "field"); ports["var"] = "ports"; ports["type"] = "text-multi"; ports["label"] = "Ports"; @@ -238,15 +216,13 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com auto vals = utils::split(options.ports.value(), ';', false); for (const auto& val: vals) { - XmlNode ports_value("value"); + XmlSubNode ports_value(ports, "value"); ports_value.set_inner(val); - ports.add_child(std::move(ports_value)); } ports.add_child(required); - x.add_child(std::move(ports)); #ifdef BOTAN_FOUND - XmlNode tls_ports("field"); + XmlSubNode tls_ports(x, "field"); tls_ports["var"] = "tls_ports"; tls_ports["type"] = "text-multi"; tls_ports["label"] = "TLS ports"; @@ -254,126 +230,105 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com vals = utils::split(options.tlsPorts.value(), ';', false); for (const auto& val: vals) { - XmlNode tls_ports_value("value"); + XmlSubNode tls_ports_value(tls_ports, "value"); tls_ports_value.set_inner(val); - tls_ports.add_child(std::move(tls_ports_value)); } tls_ports.add_child(required); - x.add_child(std::move(tls_ports)); - XmlNode verify_cert("field"); + XmlSubNode verify_cert(x, "field"); verify_cert["var"] = "verify_cert"; verify_cert["type"] = "boolean"; verify_cert["label"] = "Verify certificate"; verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid"; - XmlNode verify_cert_value("value"); + XmlSubNode verify_cert_value(verify_cert, "value"); if (options.verifyCert.value()) verify_cert_value.set_inner("true"); else verify_cert_value.set_inner("false"); - verify_cert.add_child(std::move(verify_cert_value)); - x.add_child(std::move(verify_cert)); - XmlNode fingerprint("field"); + XmlSubNode fingerprint(x, "field"); fingerprint["var"] = "fingerprint"; fingerprint["type"] = "text-single"; fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust."; if (!options.trustedFingerprint.value().empty()) { - XmlNode fingerprint_value("value"); + XmlSubNode fingerprint_value(fingerprint, "value"); fingerprint_value.set_inner(options.trustedFingerprint.value()); - fingerprint.add_child(std::move(fingerprint_value)); } fingerprint.add_child(required); - x.add_child(std::move(fingerprint)); #endif - XmlNode pass("field"); + XmlSubNode pass(x, "field"); pass["var"] = "pass"; pass["type"] = "text-private"; pass["label"] = "Server password (to be used in a PASS command when connecting)"; if (!options.pass.value().empty()) { - XmlNode pass_value("value"); + XmlSubNode pass_value(pass, "value"); pass_value.set_inner(options.pass.value()); - pass.add_child(std::move(pass_value)); } pass.add_child(required); - x.add_child(std::move(pass)); - XmlNode after_cnt_cmd("field"); + 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.afterConnectionCommand.value().empty()) { - XmlNode after_cnt_cmd_value("value"); + XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value()); - after_cnt_cmd.add_child(std::move(after_cnt_cmd_value)); } after_cnt_cmd.add_child(required); - x.add_child(std::move(after_cnt_cmd)); if (Config::get("realname_customization", "true") == "true") { - XmlNode username("field"); + XmlSubNode username(x, "field"); username["var"] = "username"; username["type"] = "text-single"; username["label"] = "Username"; if (!options.username.value().empty()) { - XmlNode username_value("value"); + XmlSubNode username_value(username, "value"); username_value.set_inner(options.username.value()); - username.add_child(std::move(username_value)); } username.add_child(required); - x.add_child(std::move(username)); - XmlNode realname("field"); + XmlSubNode realname(x, "field"); realname["var"] = "realname"; realname["type"] = "text-single"; realname["label"] = "Realname"; if (!options.realname.value().empty()) { - XmlNode realname_value("value"); + XmlSubNode realname_value(realname, "value"); realname_value.set_inner(options.realname.value()); - realname.add_child(std::move(realname_value)); } realname.add_child(required); - x.add_child(std::move(realname)); } - XmlNode encoding_out("field"); + XmlSubNode encoding_out(x, "field"); encoding_out["var"] = "encoding_out"; encoding_out["type"] = "text-single"; encoding_out["desc"] = "The encoding used when sending messages to the IRC server."; encoding_out["label"] = "Out encoding"; if (!options.encodingOut.value().empty()) { - XmlNode encoding_out_value("value"); + XmlSubNode encoding_out_value(encoding_out, "value"); encoding_out_value.set_inner(options.encodingOut.value()); - encoding_out.add_child(std::move(encoding_out_value)); } encoding_out.add_child(required); - x.add_child(std::move(encoding_out)); - XmlNode encoding_in("field"); + XmlSubNode encoding_in(x, "field"); encoding_in["var"] = "encoding_in"; encoding_in["type"] = "text-single"; encoding_in["desc"] = "The encoding used to decode message received from the IRC server."; encoding_in["label"] = "In encoding"; if (!options.encodingIn.value().empty()) { - XmlNode encoding_in_value("value"); + XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); - encoding_in.add_child(std::move(encoding_in_value)); } encoding_in.add_child(required); - x.add_child(std::move(encoding_in)); - - - command_node.add_child(std::move(x)); } void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -458,17 +413,14 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com options.update(); command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; note.set_inner("Configuration successfully applied."); - command_node.add_child(std::move(note)); return; } - XmlNode error(ADHOC_NS":error"); + XmlSubNode error(command_node, ADHOC_NS":error"); error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - error.add_child(std::move(condition)); - command_node.add_child(std::move(error)); + XmlSubNode condition(error, STANZA_NS":bad-request"); session.terminate(); } @@ -480,46 +432,38 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain, iid.get_server(), iid.get_local()); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server()); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local()); - x.add_child(std::move(instructions)); XmlNode required("required"); - XmlNode encoding_out("field"); + XmlSubNode encoding_out(x, "field"); encoding_out["var"] = "encoding_out"; encoding_out["type"] = "text-single"; encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel"; encoding_out["label"] = "Out encoding"; if (!options.encodingOut.value().empty()) { - XmlNode encoding_out_value("value"); + XmlSubNode encoding_out_value(encoding_out, "value"); encoding_out_value.set_inner(options.encodingOut.value()); - encoding_out.add_child(std::move(encoding_out_value)); } encoding_out.add_child(required); - x.add_child(std::move(encoding_out)); - XmlNode encoding_in("field"); + XmlSubNode encoding_in(x, "field"); encoding_in["var"] = "encoding_in"; encoding_in["type"] = "text-single"; encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel"; encoding_in["label"] = "In encoding"; if (!options.encodingIn.value().empty()) { - XmlNode encoding_in_value("value"); + XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); - encoding_in.add_child(std::move(encoding_in_value)); } encoding_in.add_child(required); - x.add_child(std::move(encoding_in)); - - command_node.add_child(std::move(x)); } void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -548,17 +492,14 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co options.update(); command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; note.set_inner("Configuration successfully applied."); - command_node.add_child(std::move(note)); return; } - XmlNode error(ADHOC_NS":error"); + XmlSubNode error(command_node, ADHOC_NS":error"); error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - error.add_child(std::move(condition)); - command_node.add_child(std::move(error)); + XmlSubNode condition(error, STANZA_NS":bad-request"); session.terminate(); } #endif // USE_DATABASE @@ -576,31 +517,24 @@ void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& { // Send a form to select the user to disconnect auto& biboumi_component = static_cast(xmpp_component); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Disconnect a user from selected IRC servers"); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Choose a user JID"); - x.add_child(std::move(instructions)); - XmlNode jids_field("field"); + XmlSubNode jids_field(x, "field"); jids_field["var"] = "jid"; jids_field["type"] = "list-single"; jids_field["label"] = "The JID to disconnect"; - XmlNode required("required"); - jids_field.add_child(std::move(required)); + XmlSubNode required(jids_field, "required"); for (Bridge* bridge: biboumi_component.get_bridges()) { - XmlNode option("option"); + XmlSubNode option(jids_field, "option"); option["label"] = bridge->get_jid(); - XmlNode value("value"); + XmlSubNode value(option, "value"); value.set_inner(bridge->get_jid()); - option.add_child(std::move(value)); - jids_field.add_child(std::move(option)); } - x.add_child(std::move(jids_field)); - command_node.add_child(std::move(x)); } } @@ -628,53 +562,42 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession& command_node.delete_all_children(); auto& biboumi_component = static_cast(xmpp_component); - XmlNode x("jabber:x:data:x"); + XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; - XmlNode title("title"); + XmlSubNode title(x, "title"); title.set_inner("Disconnect a user from selected IRC servers"); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); + XmlSubNode instructions(x, "instructions"); instructions.set_inner("Choose one or more servers to disconnect this JID from"); - x.add_child(std::move(instructions)); - XmlNode jids_field("field"); + XmlSubNode jids_field(x, "field"); jids_field["var"] = "irc-servers"; jids_field["type"] = "list-multi"; jids_field["label"] = "The servers to disconnect from"; - XmlNode required("required"); - jids_field.add_child(std::move(required)); + XmlSubNode required(jids_field, "required"); Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect); if (!bridge || bridge->get_irc_clients().empty()) { - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; note.set_inner("User "s + jid_to_disconnect + " is not connected to any IRC server."); - command_node.add_child(std::move(note)); session.terminate(); return ; } for (const auto& pair: bridge->get_irc_clients()) { - XmlNode option("option"); + XmlSubNode option(jids_field, "option"); option["label"] = pair.first; - XmlNode value("value"); + XmlSubNode value(option, "value"); value.set_inner(pair.first); - option.add_child(std::move(value)); - jids_field.add_child(std::move(option)); } - x.add_child(std::move(jids_field)); - XmlNode message_field("field"); + XmlSubNode message_field(x, "field"); message_field["var"] = "quit-message"; message_field["type"] = "text-single"; message_field["label"] = "Quit message"; - XmlNode message_value("value"); + XmlSubNode message_value(message_field, "value"); message_value.set_inner("Killed by admin"); - message_field.add_child(std::move(message_value)); - x.add_child(std::move(message_field)); - - command_node.add_child(std::move(x)); } void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) @@ -719,14 +642,13 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& } } command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; std::string msg = jid_to_disconnect + " was disconnected from " + std::to_string(number) + " IRC server"; if (number > 1) msg += "s"; msg += "."; note.set_inner(msg); - command_node.add_child(std::move(note)); } void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node) @@ -742,10 +664,9 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, utils::ScopeGuard sg([&message, &command_node]() { command_node.delete_all_children(); - XmlNode note("note"); + XmlSubNode note(command_node, "note"); note["type"] = "info"; note.set_inner(message); - command_node.add_child(std::move(note)); }); Bridge* bridge = biboumi_component.get_user_bridge(owner.bare()); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index bd05bdc..985b252 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -625,39 +625,33 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, const std::string& queryid) { - Stanza message("message"); + Stanza message("message"); + { message["from"] = from; message["to"] = to; - XmlNode result("result"); + XmlSubNode result(message, "result"); result["xmlns"] = MAM_NS; if (!queryid.empty()) result["queryid"] = queryid; result["id"] = log_line.uuid.value(); - XmlNode forwarded("forwarded"); + XmlSubNode forwarded(result, "forwarded"); forwarded["xmlns"] = FORWARD_NS; - XmlNode delay("delay"); + XmlSubNode delay(forwarded, "delay"); delay["xmlns"] = DELAY_NS; delay["stamp"] = utils::to_string(log_line.date.value().timeStamp()); - forwarded.add_child(std::move(delay)); - - XmlNode submessage("message"); + XmlSubNode submessage(forwarded, "message"); submessage["xmlns"] = CLIENT_NS; submessage["from"] = from + "/" + log_line.nick.value(); submessage["type"] = "groupchat"; - XmlNode body("body"); + XmlSubNode body(submessage, "body"); body.set_inner(log_line.body.value()); - submessage.add_child(std::move(body)); - - forwarded.add_child(std::move(submessage)); - result.add_child(std::move(forwarded)); - message.add_child(std::move(result)); - - this->send_stanza(message); + } + this->send_stanza(message); } #endif @@ -699,24 +693,23 @@ std::vector BiboumiComponent::get_bridges() const 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"; - query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) - { - XmlNode feature("feature"); - feature["var"] = ns; - query.add_child(std::move(feature)); - } - iq.add_child(std::move(query)); + { + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = this->served_hostname; + XmlSubNode query(iq, "query"); + query["xmlns"] = DISCO_INFO_NS; + XmlSubNode identity(query, "identity"); + 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}) + { + XmlSubNode feature(query, "feature"); + feature["var"] = ns; + } + } this->send_stanza(iq); } @@ -724,44 +717,42 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s { Jid from(jid_from); Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = jid_from; - XmlNode query("query"); - query["xmlns"] = DISCO_INFO_NS; - XmlNode identity("identity"); - identity["category"] = "conference"; - identity["type"] = "irc"; - identity["name"] = "IRC server "s + from.local + " over Biboumi"; - query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS}) - { - XmlNode feature("feature"); - feature["var"] = ns; - query.add_child(std::move(feature)); - } - iq.add_child(std::move(query)); + { + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlSubNode query(iq, "query"); + query["xmlns"] = DISCO_INFO_NS; + XmlSubNode identity(query, "identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "IRC server "s + from.local + " over Biboumi"; + for (const char *ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS}) + { + XmlSubNode feature(query, "feature"); + feature["var"] = ns; + } + } this->send_stanza(iq); } void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to) { Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["from"] = jid_from; - iq["to"] = jid_to; - - XmlNode query("query"); - query["xmlns"] = DISCO_INFO_NS; - query["node"] = MUC_TRAFFIC_NS; - // We drop all “special” traffic (like xhtml-im, chatstates, etc), so - // don’t include any - iq.add_child(std::move(query)); - + { + iq["type"] = "result"; + iq["id"] = id; + iq["from"] = jid_from; + iq["to"] = jid_to; + + XmlSubNode query(iq, "query"); + query["xmlns"] = DISCO_INFO_NS; + query["node"] = MUC_TRAFFIC_NS; + // We drop all “special” traffic (like xhtml-im, chatstates, etc), so + // don’t include any + } this->send_stanza(iq); - } void BiboumiComponent::send_ping_request(const std::string& from, @@ -769,13 +760,14 @@ void BiboumiComponent::send_ping_request(const std::string& from, 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; - iq.add_child(std::move(ping)); + { + iq["type"] = "get"; + iq["id"] = id; + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlSubNode ping(iq, "ping"); + ping["xmlns"] = PING_NS; + } this->send_stanza(iq); auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) @@ -799,48 +791,43 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std const ResultSetInfo& rs_info) { 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; + { + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + XmlSubNode query(iq, "query"); + query["xmlns"] = DISCO_ITEMS_NS; for (auto it = begin; it != end; ++it) - { - XmlNode item("item"); + { + XmlSubNode item(query, "item"); item["jid"] = it->channel + "@" + this->served_hostname; - query.add_child(std::move(item)); - } - - if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) - { - XmlNode set_node("set"); - set_node["xmlns"] = RSM_NS; + } - if (begin != channel_list.channels.cend()) - { - XmlNode first_node("first"); - first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin)); - first_node.set_inner(begin->channel + "@" + this->served_hostname); - set_node.add_child(std::move(first_node)); - } - if (end != channel_list.channels.cbegin()) - { - XmlNode last_node("last"); - last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname); - set_node.add_child(std::move(last_node)); - } - if (channel_list.complete) - { - XmlNode count_node("count"); - count_node.set_inner(std::to_string(channel_list.channels.size())); - set_node.add_child(std::move(count_node)); - } - query.add_child(std::move(set_node)); - } + if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) + { + XmlSubNode set_node(query, "set"); + set_node["xmlns"] = RSM_NS; - iq.add_child(std::move(query)); + if (begin != channel_list.channels.cend()) + { + XmlSubNode first_node(set_node, "first"); + first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin)); + first_node.set_inner(begin->channel + "@" + this->served_hostname); + } + if (end != channel_list.channels.cbegin()) + { + XmlSubNode last_node(set_node, "last"); + last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname); + } + if (channel_list.complete) + { + XmlSubNode count_node(set_node, "count"); + count_node.set_inner(std::to_string(channel_list.channels.size())); + } + } + } this->send_stanza(iq); } @@ -849,17 +836,17 @@ void BiboumiComponent::send_invitation(const std::string& room_target, const std::string& author_nick) { Stanza message("message"); - message["from"] = room_target + "@" + this->served_hostname; - message["to"] = jid_to; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode invite("invite"); - if (author_nick.empty()) - invite["from"] = room_target + "@" + this->served_hostname; - else - invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; - x.add_child(std::move(invite)); - message.add_child(std::move(x)); + { + message["from"] = room_target + "@" + this->served_hostname; + message["to"] = jid_to; + XmlSubNode x(message, "x"); + x["xmlns"] = MUC_USER_NS; + XmlSubNode invite(x, "invite"); + if (author_nick.empty()) + invite["from"] = room_target + "@" + this->served_hostname; + else + invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + } this->send_stanza(message); } @@ -875,7 +862,7 @@ void BiboumiComponent::accept_subscription(const std::string& from, const std::s void BiboumiComponent::ask_subscription(const std::string& from, const std::string& to) { - Stanza presence("presence"); + Stanza presence("presence"); presence["from"] = from; presence["to"] = to; presence["id"] = this->next_id(); -- cgit v1.2.3 From e397fc837e00cff58081810c8b54cb741299d993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 18 Dec 2016 17:21:15 +0100 Subject: Send iq error/result when the user changed a MODE command with an iq And add tests for all the mode changes --- src/bridge/bridge.cpp | 62 ++++++++++++++++++++++++++++++++++++++++-- src/bridge/bridge.hpp | 4 +-- src/xmpp/biboumi_component.cpp | 2 +- 3 files changed, 62 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1841b95..16b1c68 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -263,9 +263,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) } } -void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& nick, +void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& from, + const std::string& nick, const std::string& affiliation, - const std::string& role) + const std::string& role, + const std::string& id) { IrcClient* irc = this->get_irc_client(iid.get_server()); IrcChannel* chan = irc->get_channel(iid.get_local()); @@ -273,7 +275,11 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& return; IrcUser* user = chan->find_user(nick); if (!user) - return; + { + this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", + "item-not-found", "no such nick", false); + return; + } // For each affiliation or role, we have a “maximal” mode that we want to // set. We must remove any superior mode at the same time. For example if // the user already has +o mode, and we set its affiliation to member, we @@ -325,6 +331,56 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& std::vector args(nb, nick); args.insert(args.begin(), modes); irc->send_mode_command(iid.get_local(), args); + + irc_responder_callback_t cb = [this, iid, irc, id, from, nick](const std::string& irc_hostname, const IrcMessage& message) -> bool + { + if (irc_hostname != iid.get_server()) + return false; + + if (message.command == "MODE" && message.arguments.size() >= 2) + { + const std::string& chan_name = message.arguments[0]; + if (chan_name != iid.get_local()) + return false; + const std::string actor_nick = IrcUser{message.prefix}.nick; + if (!irc || irc->get_own_nick() != actor_nick) + return false; + + this->xmpp.send_iq_result(id, from, std::to_string(iid)); + } + else if (message.command == "401" && message.arguments.size() >= 2) + { + const std::string target_later = message.arguments[1]; + if (target_later != nick) + return false; + std::string error_message = "No such nick"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "item-not-found", + error_message, false); + } + else if (message.command == "482" && message.arguments.size() >= 2) + { + const std::string chan_name_later = utils::tolower(message.arguments[1]); + if (chan_name_later != iid.get_local()) + return false; + std::string error_message = "You're not channel operator"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed", + error_message, false); + } + else if (message.command == "472" && message.arguments.size() >= 2) + { + std::string error_message = "Unknown mode: "s + message.arguments[1]; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed", + error_message, false); + } + return true; + }; + this->add_waiting_irc(std::move(cb)); } void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index e92747d..fc839b4 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -103,8 +103,8 @@ public: bool send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info, const std::string& id, const std::string& to_jid, const std::string& from); - void forward_affiliation_role_change(const Iid& iid, const std::string& nick, - const std::string& affiliation, const std::string& role); + void forward_affiliation_role_change(const Iid& iid, const std::string& from, const std::string& nick, + const std::string& affiliation, const std::string& role, const std::string& id); /** * Directly send a CTCP PING request to the IRC user */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 985b252..6971538 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -352,7 +352,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) bridge->send_irc_kick(iid, nick, reason, id, from); } else - bridge->forward_affiliation_role_change(iid, nick, affiliation, role); + bridge->forward_affiliation_role_change(iid, from, nick, affiliation, role, id); stanza_error.disable(); } } -- cgit v1.2.3 From 5b56007828f20c763df3f36ceed809188880663e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 6 Jan 2017 22:58:18 +0100 Subject: Use udns instead of c-ares fix #3226 --- src/main.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 060372b..bc8e779 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,7 +6,7 @@ #include #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND # include #endif @@ -129,15 +129,16 @@ int main(int ac, char** av) auto p = std::make_shared(); +#ifdef UDNS_FOUND + DNSHandler dns_handler(p); +#endif + auto xmpp_component = std::make_shared(p, hostname, password); xmpp_component->start(); IdentdServer identd(*xmpp_component, p, static_cast(Config::get_int("identd_port", 113))); -#ifdef CARES_FOUND - DNSHandler::instance.watch_dns_sockets(p); -#endif auto timeout = TimedEventsManager::instance().get_timeout(); while (p->poll(timeout) != -1) { @@ -155,6 +156,9 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); +#ifdef UDNS_FOUND + dns_handler.destroy(); +#endif identd.shutdown(); // Cancel the timer for a potential reconnection TimedEventsManager::instance().cancel("XMPP reconnection"); @@ -200,18 +204,11 @@ int main(int ac, char** av) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); -#ifdef CARES_FOUND - if (!exiting) - DNSHandler::instance.watch_dns_sockets(p); -#endif if (exiting) // If we are exiting, do not wait for any timed event timeout = utils::no_timeout; else timeout = TimedEventsManager::instance().get_timeout(); } -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif if (!xmpp_component->ever_auth) return 1; // To signal that the process did not properly start log_info("All connections cleanly closed, have a nice day."); -- cgit v1.2.3 From e31ff3e9e94d943d4f307eb6ab8cee7fbd11b565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 6 Jan 2017 23:45:26 +0100 Subject: Fix some issues found by sonar cube --- src/xmpp/biboumi_component.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6971538..bd6975e 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -136,7 +136,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) // 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([&](){ + utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name](){ this->send_stanza_error("presence", from_str, to_str, id, error_type, error_name, ""); }); @@ -205,7 +205,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ + utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name](){ this->send_stanza_error("message", from_str, to_str, id, error_type, error_name, ""); }); @@ -324,7 +324,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) // the scopeguard. std::string error_type("cancel"); std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ + utils::ScopeGuard stanza_error([this, &from, &to_str, &id, &error_type, &error_name](){ this->send_stanza_error("iq", from, to_str, id, error_type, error_name, ""); }); -- cgit v1.2.3 From 6ececd9f3990513ce35a38c2faac7d265e09900e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 10 Jan 2017 00:18:59 +0100 Subject: Only try to join chans only once, even if we received multiple presences ref #3228 --- src/irc/irc_client.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ba13c6a..4fd1333 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -450,7 +450,12 @@ void IrcClient::send_quit_command(const std::string& reason) void IrcClient::send_join_command(const std::string& chan_name, const std::string& password) { if (this->welcomed == false) - this->channels_to_join.emplace_back(chan_name, password); + { + const auto it = std::find_if(begin(this->channels_to_join), end(this->channels_to_join), + [&chan_name](const auto& pair) { return std::get<0>(pair) == chan_name; }); + if (it == end(this->channels_to_join)) + this->channels_to_join.emplace_back(chan_name, password); + } else if (password.empty()) this->send_message(IrcMessage("JOIN", {chan_name})); else -- cgit v1.2.3 From 3ae4937fb490919e2a76b720fa28da1239d4ba78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 10 Jan 2017 00:21:15 +0100 Subject: Add missing include for last commit --- src/irc/irc_client.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4fd1333..6813bba 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 5d801ddcd025f68d2ec91edf0462091a32c779c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 22 Jan 2017 21:31:50 +0100 Subject: Add a linger_time configuration option on IRC servers --- src/bridge/bridge.cpp | 22 +++++++++++++++++++++- src/bridge/bridge.hpp | 5 +++++ src/xmpp/biboumi_adhoc_commands.cpp | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 16b1c68..ca9c9fa 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -11,6 +11,7 @@ #include #include "result_set_management.hpp" #include +#include using namespace std::string_literals; @@ -865,7 +866,7 @@ void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& me this->user_jid + "/" + res, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) - irc->send_quit_command(""); + this->quit_or_start_linger_timer(iid.get_server()); } void Bridge::send_nick_change(Iid&& iid, @@ -1212,3 +1213,22 @@ void Bridge::set_record_history(const bool val) this->record_history = val; } #endif + +void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname) +{ +#ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->get_bare_jid(), + irc_hostname); + const auto timeout = std::chrono::seconds(options.lingerTime.value()); +#else + const auto timeout = 0s; +#endif + + const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid(); + TimedEvent event(std::chrono::steady_clock::now() + timeout, [this, irc_hostname]() { + IrcClient* irc = this->find_irc_client(irc_hostname); + if (irc) + irc->send_quit_command(""); + }, event_name); + TimedEventsManager::instance().add_event(std::move(event)); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index fc839b4..7d0166c 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -236,6 +236,11 @@ public: #ifdef USE_DATABASE void set_record_history(const bool val); #endif + /** + * Start a timer that will send a QUIT command after the + * configured linger time is expired. + */ + void quit_or_start_linger_timer(const std::string& irc_hostname); private: /** diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index f6f3cd1..ccb3517 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -329,6 +329,17 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com encoding_in_value.set_inner(options.encodingIn.value()); } encoding_in.add_child(required); + + XmlSubNode linger_time(x, "field"); + linger_time["var"] = "linger_time"; + linger_time["type"] = "text-single"; + linger_time["desc"] = "The number of seconds to wait before sending a QUIT command, after the last channel on that server has been left."; + linger_time["label"] = "Linger time"; + { + XmlSubNode linger_time_value(linger_time, "value"); + linger_time_value.set_inner(std::to_string(options.lingerTime.value())); + } + encoding_in.add_child(required); } void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -408,6 +419,10 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com value && !value->get_inner().empty()) options.encodingIn = value->get_inner(); + else if (field->get_tag("var") == "linger_time" && + value && !value->get_inner().empty()) + options.lingerTime = value->get_inner(); + } options.update(); -- cgit v1.2.3 From b660a4736778cde8d0805390ffa857b77c271757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 22 Jan 2017 21:30:57 +0100 Subject: grammar: than <-> as --- src/bridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ca9c9fa..cafcbc3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1029,7 +1029,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& const std::string& id) { // Use revstr because the forwarded ping to target XMPP user must not be - // the same that the request iq, but we also need to get it back easily + // the same as the request iq, but we also need to get it back easily // (revstr again) // Forward to the first resource (arbitrary, based on the “order” of the std::set) only const auto resources = this->resources_in_server[hostname]; -- cgit v1.2.3 From 45f7396c8d30ed37570c4ecdaa886388f9beba3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 25 Jan 2017 19:55:45 +0100 Subject: Cancel the IRC server linger timer when we try to-rejoin a channel on it --- src/bridge/bridge.cpp | 7 +++++++ src/bridge/bridge.hpp | 1 + 2 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index cafcbc3..573e8d7 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -169,6 +169,7 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const const std::string& resource) { const auto hostname = iid.get_server(); + this->cancel_linger_timer(hostname); IrcClient* irc = this->make_irc_client(hostname, nickname); this->add_resource_to_server(hostname, resource); auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource); @@ -1232,3 +1233,9 @@ void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname) }, event_name); TimedEventsManager::instance().add_event(std::move(event)); } + +void Bridge::cancel_linger_timer(const std::string& irc_hostname) +{ + const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid(); + TimedEventsManager::instance().cancel(event_name); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 7d0166c..b165650 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -241,6 +241,7 @@ public: * configured linger time is expired. */ void quit_or_start_linger_timer(const std::string& irc_hostname); + void cancel_linger_timer(const std::string& irc_hostname); private: /** -- cgit v1.2.3 From e2da6fcddde7603510a266488980160d4cd462cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 1 Feb 2017 20:52:50 +0100 Subject: Properly destroy the dns_handler socket when first start fails To correctly exit the process --- src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index bc8e779..76ab5d9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -196,7 +196,12 @@ int main(int ac, char** av) } } else - identd.shutdown(); + { +#ifdef UDNS_FOUND + dns_handler.destroy(); +#endif + identd.shutdown(); + } } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. -- cgit v1.2.3 From 7d41577bc46d6ba300da2b0f13d2e318b087b118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 12 Feb 2017 15:11:13 +0100 Subject: The form fields in the Configure ad-hoc commands are not required --- src/xmpp/biboumi_adhoc_commands.cpp | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index ccb3517..a83af80 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -206,8 +206,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain); - XmlNode required("required"); - XmlSubNode ports(x, "field"); ports["var"] = "ports"; ports["type"] = "text-multi"; @@ -219,7 +217,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode ports_value(ports, "value"); ports_value.set_inner(val); } - ports.add_child(required); #ifdef BOTAN_FOUND XmlSubNode tls_ports(x, "field"); @@ -233,7 +230,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode tls_ports_value(tls_ports, "value"); tls_ports_value.set_inner(val); } - tls_ports.add_child(required); XmlSubNode verify_cert(x, "field"); verify_cert["var"] = "verify_cert"; @@ -255,7 +251,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode fingerprint_value(fingerprint, "value"); fingerprint_value.set_inner(options.trustedFingerprint.value()); } - fingerprint.add_child(required); #endif XmlSubNode pass(x, "field"); @@ -267,7 +262,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode pass_value(pass, "value"); pass_value.set_inner(options.pass.value()); } - pass.add_child(required); XmlSubNode after_cnt_cmd(x, "field"); after_cnt_cmd["var"] = "after_connect_command"; @@ -279,7 +273,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value()); } - after_cnt_cmd.add_child(required); if (Config::get("realname_customization", "true") == "true") { @@ -292,7 +285,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode username_value(username, "value"); username_value.set_inner(options.username.value()); } - username.add_child(required); XmlSubNode realname(x, "field"); realname["var"] = "realname"; @@ -303,7 +295,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode realname_value(realname, "value"); realname_value.set_inner(options.realname.value()); } - realname.add_child(required); } XmlSubNode encoding_out(x, "field"); @@ -316,7 +307,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode encoding_out_value(encoding_out, "value"); encoding_out_value.set_inner(options.encodingOut.value()); } - encoding_out.add_child(required); XmlSubNode encoding_in(x, "field"); encoding_in["var"] = "encoding_in"; @@ -328,7 +318,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); } - encoding_in.add_child(required); XmlSubNode linger_time(x, "field"); linger_time["var"] = "linger_time"; @@ -339,7 +328,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode linger_time_value(linger_time, "value"); linger_time_value.set_inner(std::to_string(options.lingerTime.value())); } - encoding_in.add_child(required); } void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -454,8 +442,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local()); - XmlNode required("required"); - XmlSubNode encoding_out(x, "field"); encoding_out["var"] = "encoding_out"; encoding_out["type"] = "text-single"; @@ -466,7 +452,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co XmlSubNode encoding_out_value(encoding_out, "value"); encoding_out_value.set_inner(options.encodingOut.value()); } - encoding_out.add_child(required); XmlSubNode encoding_in(x, "field"); encoding_in["var"] = "encoding_in"; @@ -478,7 +463,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); } - encoding_in.add_child(required); } void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) -- cgit v1.2.3 From 8b2f748b1d8de6513ca69e643b50477b0e5a2130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Feb 2017 09:58:54 +0100 Subject: Do not send a not-connected error, on "unavailable" presences fix #3231 --- src/xmpp/biboumi_component.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index bd6975e..52bf2b7 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -179,10 +179,11 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } catch (const IRCNotConnected& ex) { - this->send_stanza_error("presence", from_str, to_str, id, - "cancel", "remote-server-not-found", - "Not connected to IRC server "s + ex.hostname, - true); + if (type == "unavailable") + this->send_stanza_error("presence", from_str, to_str, id, + "cancel", "remote-server-not-found", + "Not connected to IRC server "s + ex.hostname, + true); } stanza_error.disable(); } -- cgit v1.2.3 From 3a288340df860f6aeaf0f9a59734c1d39d091495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Feb 2017 10:21:25 +0100 Subject: Fix the previous commit fix #3231 --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 52bf2b7..663e92e 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -179,7 +179,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } catch (const IRCNotConnected& ex) { - if (type == "unavailable") + if (type != "unavailable") this->send_stanza_error("presence", from_str, to_str, id, "cancel", "remote-server-not-found", "Not connected to IRC server "s + ex.hostname, -- cgit v1.2.3 From d81c3ad5ac2c12130d90044b7597bf962a7cfe9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 4 Mar 2017 14:00:53 +0100 Subject: Fix the order of from and to address in muc traffic info reply And add a test for it. fix #3238 --- src/xmpp/biboumi_component.cpp | 2 +- src/xmpp/biboumi_component.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 663e92e..2783b93 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -738,7 +738,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s this->send_stanza(iq); } -void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to) +void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from) { Stanza iq("iq"); { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 7cafdec..aa0c3db 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -69,7 +69,7 @@ public: * Sends the allowed namespaces in MUC message, according to * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic */ - void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to); + void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from); /** * Send a ping request */ -- cgit v1.2.3 From 99a4ddedaf903d27b781341108433ae2d9533ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 6 Mar 2017 00:51:43 +0100 Subject: Remove the embedded sha1 code, and use one of botan or gcrypt This adds a hard dependency on one of Botan or gcrypt. Botan is already a recommended dependency, and gcrypt is probably packaged almost everywhere, so this should not be a big deal. ref #3241 --- src/identd/identd_socket.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'src') diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp index 46553ca..a94f172 100644 --- a/src/identd/identd_socket.cpp +++ b/src/identd/identd_socket.cpp @@ -38,14 +38,7 @@ void IdentdSocket::parse_in_buffer(const std::size_t) static std::string hash_jid(const std::string& jid) { - sha1nfo sha1; - sha1_init(&sha1); - sha1_write(&sha1, jid.data(), jid.size()); - const uint8_t* res = sha1_result(&sha1); - std::ostringstream result; - for (int i = 0; i < HASH_LENGTH; i++) - result << std::hex << std::setfill('0') << std::setw(2) << static_cast(res[i]); - return result.str(); + return sha1(jid); } std::string IdentdSocket::generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote) -- cgit v1.2.3 From f0bc6c83a8eb548d0a3edbf7c16a6922bfd24ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Mar 2017 19:04:15 +0100 Subject: Pass the shared_ptr by reference, to avoid useless copies --- src/bridge/bridge.cpp | 2 +- src/bridge/bridge.hpp | 2 +- src/identd/identd_server.hpp | 2 +- src/identd/identd_socket.cpp | 2 +- src/identd/identd_socket.hpp | 2 +- src/irc/irc_client.cpp | 2 +- src/irc/irc_client.hpp | 2 +- src/xmpp/biboumi_component.cpp | 2 +- src/xmpp/biboumi_component.hpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 573e8d7..7e2d8c1 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -29,7 +29,7 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) #endif } -Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr poller): +Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller): user_jid(user_jid), xmpp(xmpp), poller(poller) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b165650..73daae7 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -38,7 +38,7 @@ using irc_responder_callback_t = std::function poller); + explicit Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller); ~Bridge() = default; Bridge(const Bridge&) = delete; diff --git a/src/identd/identd_server.hpp b/src/identd/identd_server.hpp index 5f74976..b1c8ec8 100644 --- a/src/identd/identd_server.hpp +++ b/src/identd/identd_server.hpp @@ -10,7 +10,7 @@ class BiboumiComponent; class IdentdServer: public TcpSocketServer { public: - IdentdServer(const BiboumiComponent& biboumi_component, std::shared_ptr poller, const uint16_t port): + IdentdServer(const BiboumiComponent& biboumi_component, std::shared_ptr& poller, const uint16_t port): TcpSocketServer(poller, port), biboumi_component(biboumi_component) {} diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp index a94f172..b85257c 100644 --- a/src/identd/identd_socket.cpp +++ b/src/identd/identd_socket.cpp @@ -8,7 +8,7 @@ #include -IdentdSocket::IdentdSocket(std::shared_ptr poller, const socket_t socket, TcpSocketServer& server): +IdentdSocket::IdentdSocket(std::shared_ptr& poller, const socket_t socket, TcpSocketServer& server): TCPSocketHandler(poller), server(dynamic_cast(server)) { diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp index 1c2bd27..10cb797 100644 --- a/src/identd/identd_socket.hpp +++ b/src/identd/identd_socket.hpp @@ -17,7 +17,7 @@ class TcpSocketServer; class IdentdSocket: public TCPSocketHandler { public: - IdentdSocket(std::shared_ptr poller, const socket_t socket, TcpSocketServer& server); + IdentdSocket(std::shared_ptr& poller, const socket_t socket, TcpSocketServer& server); ~IdentdSocket() = default; std::string generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6813bba..d0970c1 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -128,7 +128,7 @@ static const std::unordered_map poller, const std::string& hostname, +IrcClient::IrcClient(std::shared_ptr& poller, const std::string& hostname, const std::string& nickname, const std::string& username, const std::string& realname, const std::string& user_hostname, Bridge& bridge): diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4b942ad..009d0c9 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -26,7 +26,7 @@ class Bridge; class IrcClient: public TCPClientSocketHandler { public: - explicit IrcClient(std::shared_ptr poller, const std::string& hostname, + explicit IrcClient(std::shared_ptr& poller, const std::string& hostname, const std::string& nickname, const std::string& username, const std::string& realname, const std::string& user_hostname, Bridge& bridge); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 2783b93..4ba5e65 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -44,7 +44,7 @@ static std::set kickable_errors{ }; -BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): +BiboumiComponent::BiboumiComponent(std::shared_ptr& poller, const std::string& hostname, const std::string& secret): XmppComponent(poller, hostname, secret), irc_server_adhoc_commands_handler(*this), irc_channel_adhoc_commands_handler(*this) diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index aa0c3db..1d25e0e 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -27,7 +27,7 @@ using iq_responder_callback_t = std::function poller, const std::string& hostname, const std::string& secret); + explicit BiboumiComponent(std::shared_ptr& poller, const std::string& hostname, const std::string& secret); ~BiboumiComponent() = default; BiboumiComponent(const BiboumiComponent&) = delete; -- cgit v1.2.3 From 38ff50f5d2ca356f659429ff57546bd2364a0fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Mar 2017 22:09:57 +0100 Subject: =?UTF-8?q?Don=E2=80=99t=20send=20the=20unavailable=20presence=20t?= =?UTF-8?q?o=20all=20resources,=20in=20the=20virtual=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bridge/bridge.cpp | 2 +- src/irc/irc_client.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7e2d8c1..701ee11 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -62,7 +62,7 @@ void Bridge::shutdown(const std::string& exit_message) for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { it->second->send_quit_command(exit_message); - it->second->leave_dummy_channel(exit_message); + it->second->leave_dummy_channel(exit_message, ""); } } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d0970c1..9540f7a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1161,14 +1161,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel() return this->dummy_channel; } -void IrcClient::leave_dummy_channel(const std::string& exit_message) +void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource) { if (!this->dummy_channel.joined) return; this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true); + this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, resource); } #ifdef BOTAN_FOUND -- cgit v1.2.3 From a0a2de3b4d2facb25bbead59873cbf7f58f1d62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Mar 2017 22:28:00 +0100 Subject: =?UTF-8?q?Revert=20"Don=E2=80=99t=20send=20the=20unavailable=20pr?= =?UTF-8?q?esence=20to=20all=20resources,=20in=20the=20virtual=20channel"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 38ff50f5d2ca356f659429ff57546bd2364a0fef. --- src/bridge/bridge.cpp | 2 +- src/irc/irc_client.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 701ee11..7e2d8c1 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -62,7 +62,7 @@ void Bridge::shutdown(const std::string& exit_message) for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { it->second->send_quit_command(exit_message); - it->second->leave_dummy_channel(exit_message, ""); + it->second->leave_dummy_channel(exit_message); } } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 9540f7a..d0970c1 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1161,14 +1161,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel() return this->dummy_channel; } -void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource) +void IrcClient::leave_dummy_channel(const std::string& exit_message) { if (!this->dummy_channel.joined) return; this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, resource); + this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true); } #ifdef BOTAN_FOUND -- cgit v1.2.3 From 0ab40dc1ab4e689921da54080b135e1d22b1c586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Mar 2017 21:45:23 +0100 Subject: Refactoring louloulibs and cmake Use OBJECT libraries Remove the louloulibs directory Write FOUND variables in the cache --- src/config/config.cpp | 103 +++++ src/config/config.hpp | 93 +++++ src/irc/irc_client.cpp | 1 - src/logger/logger.cpp | 42 ++ src/logger/logger.hpp | 128 ++++++ src/network/credentials_manager.cpp | 140 +++++++ src/network/credentials_manager.hpp | 55 +++ src/network/dns_handler.cpp | 46 ++ src/network/dns_handler.hpp | 37 ++ src/network/dns_socket_handler.cpp | 43 ++ src/network/dns_socket_handler.hpp | 33 ++ src/network/poller.cpp | 234 +++++++++++ src/network/poller.hpp | 98 +++++ src/network/resolver.cpp | 281 +++++++++++++ src/network/resolver.hpp | 122 ++++++ src/network/socket_handler.hpp | 42 ++ src/network/tcp_client_socket_handler.cpp | 261 ++++++++++++ src/network/tcp_client_socket_handler.hpp | 82 ++++ src/network/tcp_server_socket.hpp | 70 ++++ src/network/tcp_socket_handler.cpp | 358 ++++++++++++++++ src/network/tcp_socket_handler.hpp | 251 +++++++++++ src/utils/encoding.cpp | 254 +++++++++++ src/utils/encoding.hpp | 43 ++ src/utils/get_first_non_empty.cpp | 11 + src/utils/get_first_non_empty.hpp | 20 + src/utils/revstr.cpp | 9 + src/utils/revstr.hpp | 11 + src/utils/scopeguard.hpp | 98 +++++ src/utils/sha1.cpp | 32 ++ src/utils/sha1.hpp | 5 + src/utils/split.cpp | 19 + src/utils/split.hpp | 12 + src/utils/string.cpp | 28 ++ src/utils/string.hpp | 10 + src/utils/system.cpp | 21 + src/utils/system.hpp | 8 + src/utils/time.cpp | 70 ++++ src/utils/time.hpp | 10 + src/utils/timed_events.cpp | 47 +++ src/utils/timed_events.hpp | 132 ++++++ src/utils/timed_events_manager.cpp | 73 ++++ src/utils/tolower.cpp | 13 + src/utils/tolower.hpp | 11 + src/utils/xdg.cpp | 29 ++ src/utils/xdg.hpp | 14 + src/xmpp/adhoc_command.cpp | 80 ++++ src/xmpp/adhoc_command.hpp | 44 ++ src/xmpp/adhoc_commands_handler.cpp | 111 +++++ src/xmpp/adhoc_commands_handler.hpp | 71 ++++ src/xmpp/adhoc_session.cpp | 35 ++ src/xmpp/adhoc_session.hpp | 88 ++++ src/xmpp/auth.cpp | 8 + src/xmpp/auth.hpp | 6 + src/xmpp/biboumi_component.cpp | 1 - src/xmpp/body.hpp | 12 + src/xmpp/jid.cpp | 153 +++++++ src/xmpp/jid.hpp | 49 +++ src/xmpp/xmpp_component.cpp | 672 ++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 245 +++++++++++ src/xmpp/xmpp_parser.cpp | 172 ++++++++ src/xmpp/xmpp_parser.hpp | 133 ++++++ src/xmpp/xmpp_stanza.cpp | 229 ++++++++++ src/xmpp/xmpp_stanza.hpp | 160 +++++++ 63 files changed, 5767 insertions(+), 2 deletions(-) create mode 100644 src/config/config.cpp create mode 100644 src/config/config.hpp create mode 100644 src/logger/logger.cpp create mode 100644 src/logger/logger.hpp create mode 100644 src/network/credentials_manager.cpp create mode 100644 src/network/credentials_manager.hpp create mode 100644 src/network/dns_handler.cpp create mode 100644 src/network/dns_handler.hpp create mode 100644 src/network/dns_socket_handler.cpp create mode 100644 src/network/dns_socket_handler.hpp create mode 100644 src/network/poller.cpp create mode 100644 src/network/poller.hpp create mode 100644 src/network/resolver.cpp create mode 100644 src/network/resolver.hpp create mode 100644 src/network/socket_handler.hpp create mode 100644 src/network/tcp_client_socket_handler.cpp create mode 100644 src/network/tcp_client_socket_handler.hpp create mode 100644 src/network/tcp_server_socket.hpp create mode 100644 src/network/tcp_socket_handler.cpp create mode 100644 src/network/tcp_socket_handler.hpp create mode 100644 src/utils/encoding.cpp create mode 100644 src/utils/encoding.hpp create mode 100644 src/utils/get_first_non_empty.cpp create mode 100644 src/utils/get_first_non_empty.hpp create mode 100644 src/utils/revstr.cpp create mode 100644 src/utils/revstr.hpp create mode 100644 src/utils/scopeguard.hpp create mode 100644 src/utils/sha1.cpp create mode 100644 src/utils/sha1.hpp create mode 100644 src/utils/split.cpp create mode 100644 src/utils/split.hpp create mode 100644 src/utils/string.cpp create mode 100644 src/utils/string.hpp create mode 100644 src/utils/system.cpp create mode 100644 src/utils/system.hpp create mode 100644 src/utils/time.cpp create mode 100644 src/utils/time.hpp create mode 100644 src/utils/timed_events.cpp create mode 100644 src/utils/timed_events.hpp create mode 100644 src/utils/timed_events_manager.cpp create mode 100644 src/utils/tolower.cpp create mode 100644 src/utils/tolower.hpp create mode 100644 src/utils/xdg.cpp create mode 100644 src/utils/xdg.hpp create mode 100644 src/xmpp/adhoc_command.cpp create mode 100644 src/xmpp/adhoc_command.hpp create mode 100644 src/xmpp/adhoc_commands_handler.cpp create mode 100644 src/xmpp/adhoc_commands_handler.hpp create mode 100644 src/xmpp/adhoc_session.cpp create mode 100644 src/xmpp/adhoc_session.hpp create mode 100644 src/xmpp/auth.cpp create mode 100644 src/xmpp/auth.hpp create mode 100644 src/xmpp/body.hpp create mode 100644 src/xmpp/jid.cpp create mode 100644 src/xmpp/jid.hpp create mode 100644 src/xmpp/xmpp_component.cpp create mode 100644 src/xmpp/xmpp_component.hpp create mode 100644 src/xmpp/xmpp_parser.cpp create mode 100644 src/xmpp/xmpp_parser.hpp create mode 100644 src/xmpp/xmpp_stanza.cpp create mode 100644 src/xmpp/xmpp_stanza.hpp (limited to 'src') diff --git a/src/config/config.cpp b/src/config/config.cpp new file mode 100644 index 0000000..24a1c87 --- /dev/null +++ b/src/config/config.cpp @@ -0,0 +1,103 @@ +#include + +#include +#include + +#include + +std::string Config::filename{}; +std::map Config::values{}; +std::vector Config::callbacks{}; + +std::string Config::get(const std::string& option, const std::string& def) +{ + auto it = Config::values.find(option); + + if (it == Config::values.end()) + return def; + return it->second; +} + +int Config::get_int(const std::string& option, const int& def) +{ + std::string res = Config::get(option, ""); + if (!res.empty()) + return std::atoi(res.c_str()); + else + return def; +} + +void Config::set(const std::string& option, const std::string& value, bool save) +{ + Config::values[option] = value; + if (save) + { + Config::save_to_file(); + Config::trigger_configuration_change(); + } +} + +void Config::connect(t_config_changed_callback callback) +{ + Config::callbacks.push_back(callback); +} + +void Config::clear() +{ + Config::values.clear(); +} + +/** + * Private methods + */ +void Config::trigger_configuration_change() +{ + std::vector::iterator it; + for (it = Config::callbacks.begin(); it < Config::callbacks.end(); ++it) + (*it)(); +} + +bool Config::read_conf(const std::string& name) +{ + if (!name.empty()) + Config::filename = name; + + std::ifstream file(Config::filename.data()); + if (!file.is_open()) + { + std::cerr << "Error while opening file " << filename << " for reading: " << strerror(errno) << std::endl; + return false; + } + + Config::clear(); + + std::string line; + size_t pos; + std::string option; + std::string value; + while (file.good()) + { + std::getline(file, line); + if (line == "" || line[0] == '#') + continue ; + pos = line.find('='); + if (pos == std::string::npos) + continue ; + option = line.substr(0, pos); + value = line.substr(pos+1); + Config::values[option] = value; + } + return true; +} + +void Config::save_to_file() +{ + std::ofstream file(Config::filename.data()); + if (file.fail()) + { + std::cerr << "Could not save config file." << std::endl; + return ; + } + for (const auto& it: Config::values) + file << it.first << "=" << it.second << '\n'; +} diff --git a/src/config/config.hpp b/src/config/config.hpp new file mode 100644 index 0000000..4e01281 --- /dev/null +++ b/src/config/config.hpp @@ -0,0 +1,93 @@ +/** + * Read the config file and save all the values in a map. + * Also, a singleton. + * + * Use Config::filename = "bla" to set the filename you want to use. + * + * If you want to exit if the file does not exist when it is open for + * reading, set Config::file_must_exist = true. + * + * Config::get() can then be used to access the values in the conf. + * + * Use Config::close() when you're done getting/setting value. This will + * save the config into the file. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +typedef std::function t_config_changed_callback; + +class Config +{ +public: + Config() = default; + ~Config() = default; + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; + + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + */ + static std::string get(const std::string&, const std::string&); + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + */ + static int get_int(const std::string&, const int&); + /** + * Set a value for the given option. And write all the config + * in the file from which it was read if save is true. + */ + static void set(const std::string&, const std::string&, bool save = false); + /** + * Adds a function to a list. This function will be called whenever a + * configuration change occurs (when set() is called, or when the initial + * conf is read) + */ + static void connect(t_config_changed_callback); + /** + * Destroy the instance, forcing it to be recreated (with potentially + * different parameters) the next time it’s needed. + */ + static void clear(); + /** + * Read the configuration file at the given path. + */ + static bool read_conf(const std::string& name=""); + /** + * Get the filename + */ + static const std::string& get_filename() + { return Config::filename; } + +private: + /** + * Set the value of the filename to use, before calling any method. + */ + static std::string filename; + /** + * Write all the config values into the configuration file + */ + static void save_to_file(); + /** + * Call all the callbacks previously registered using connect(). + * This is used to notify any class that a configuration change occured. + */ + static void trigger_configuration_change(); + + static std::map values; + static std::vector callbacks; + +}; + + diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d0970c1..93e463b 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -21,7 +21,6 @@ #include #include "biboumi.h" -#include "louloulibs.h" using namespace std::string_literals; using namespace std::chrono_literals; diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 0000000..92a3d9b --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,42 @@ +#include +#include + +Logger::Logger(const int log_level): + log_level(log_level), + stream(std::cout.rdbuf()), + null_buffer{}, + null_stream{&null_buffer} +{ +} + +Logger::Logger(const int log_level, const std::string& log_file): + log_level(log_level), + ofstream(log_file.data(), std::ios_base::app), + stream(ofstream.rdbuf()), + null_buffer{}, + null_stream{&null_buffer} +{ +} + +std::unique_ptr& Logger::instance() +{ + static std::unique_ptr instance; + + if (!instance) + { + const std::string log_file = Config::get("log_file", ""); + const int log_level = Config::get_int("log_level", 0); + if (log_file.empty()) + instance = std::make_unique(log_level); + else + instance = std::make_unique(log_level, log_file); + } + return instance; +} + +std::ostream& Logger::get_stream(const int lvl) +{ + if (lvl >= this->log_level) + return this->stream; + return this->null_stream; +} diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp new file mode 100644 index 0000000..ff6a82b --- /dev/null +++ b/src/logger/logger.hpp @@ -0,0 +1,128 @@ +#pragma once + + +/** + * Singleton used in logger macros to write into files or stdout, with + * various levels of severity. + * Only the macros should be used. + * @class Logger + */ + +#include +#include +#include + +#define debug_lvl 0 +#define info_lvl 1 +#define warning_lvl 2 +#define error_lvl 3 + +#include "biboumi.h" +#ifdef SYSTEMD_FOUND +# include +#else +# define SD_DEBUG "[DEBUG]: " +# define SD_INFO "[INFO]: " +# define SD_WARNING "[WARNING]: " +# define SD_ERR "[ERROR]: " +#endif + +// Macro defined to get the filename instead of the full path. But if it is +// not properly defined by the build system, we fallback to __FILE__ +#ifndef __FILENAME__ +# define __FILENAME__ __FILE__ +#endif + + +/** + * A buffer, used to construct an ostream that does nothing + * when we output data in it + */ +class NullBuffer: public std::streambuf +{ + public: + int overflow(int c) { return c; } +}; + +class Logger +{ +public: + static std::unique_ptr& instance(); + std::ostream& get_stream(const int); + Logger(const int log_level, const std::string& log_file); + Logger(const int log_level); + + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + Logger(Logger&&) = delete; + Logger& operator=(Logger&&) = delete; + +private: + const int log_level; + std::ofstream ofstream{}; + std::ostream stream; + + NullBuffer null_buffer; + std::ostream null_stream; +}; + +#define WHERE __FILENAME__, ":", __LINE__, ":\t" + +namespace logging_details +{ + template + void log(std::ostream& os, const T& arg) + { + os << arg << std::endl; + } + + template + void log(std::ostream& os, const T& first, U&&... rest) + { + os << first; + log(os, std::forward(rest)...); + } + + template + void log_debug(U&&... args) + { + auto& os = Logger::instance()->get_stream(debug_lvl); + os << SD_DEBUG; + log(os, std::forward(args)...); + } + + template + void log_info(U&&... args) + { + auto& os = Logger::instance()->get_stream(info_lvl); + os << SD_INFO; + log(os, std::forward(args)...); + } + + template + void log_warning(U&&... args) + { + auto& os = Logger::instance()->get_stream(warning_lvl); + os << SD_WARNING; + log(os, std::forward(args)...); + } + + template + void log_error(U&&... args) + { + auto& os = Logger::instance()->get_stream(error_lvl); + os << SD_ERR; + log(os, std::forward(args)...); + } +} + +#define log_info(...) logging_details::log_info(WHERE, __VA_ARGS__) + +#define log_warning(...) logging_details::log_warning(WHERE, __VA_ARGS__) + +#define log_error(...) logging_details::log_error(WHERE, __VA_ARGS__) + +#define log_debug(...) logging_details::log_debug(WHERE, __VA_ARGS__) + + + diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp new file mode 100644 index 0000000..f9f8c94 --- /dev/null +++ b/src/network/credentials_manager.cpp @@ -0,0 +1,140 @@ +#include "biboumi.h" + +#ifdef BOTAN_FOUND +#include +#include +#include +#include +#include + +#ifdef USE_DATABASE +# include +#endif + +/** + * TODO find a standard way to find that out. + */ +static const std::vector default_cert_files = { + "/etc/ssl/certs/ca-bundle.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/etc/ssl/certs/ca-certificates.crt", + "/etc/ca-certificates/extracted/tls-ca-bundle.pem" +}; + +Botan::Certificate_Store_In_Memory BasicCredentialsManager::certificate_store; +bool BasicCredentialsManager::certs_loaded = false; + +BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const socket_handler): + Botan::Credentials_Manager(), + socket_handler(socket_handler), + trusted_fingerprint{} +{ + BasicCredentialsManager::load_certs(); +} + +void BasicCredentialsManager::set_trusted_fingerprint(const std::string& fingerprint) +{ + this->trusted_fingerprint = fingerprint; +} + +const std::string& BasicCredentialsManager::get_trusted_fingerprint() const +{ + return this->trusted_fingerprint; +} + +void check_tls_certificate(const std::vector& certs, + const std::string& hostname, const std::string& trusted_fingerprint, + std::exception_ptr exc) +{ + + if (!trusted_fingerprint.empty() && !certs.empty() && + trusted_fingerprint == certs[0].fingerprint() && + certs[0].matches_dns_name(hostname)) + // We trust the certificate, based on the trusted fingerprint and + // the fact that the hostname matches + return; + + if (exc) + std::rethrow_exception(exc); +} + +#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) +void BasicCredentialsManager::verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector& certs) +{ + log_debug("Checking remote certificate (", type, ") for hostname ", purported_hostname); + try + { + Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); + log_debug("Certificate is valid"); + } + catch (const std::exception& tls_exception) + { + log_warning("TLS certificate check failed: ", tls_exception.what()); + std::exception_ptr exception_ptr{}; + if (this->socket_handler->abort_on_invalid_cert()) + exception_ptr = std::current_exception(); + + check_tls_certificate(certs, purported_hostname, this->trusted_fingerprint, exception_ptr); + } +} +#endif + +bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector& paths) +{ + for (const auto& path: paths) + { + try + { + Botan::DataSource_Stream bundle(path); + log_debug("Using ca bundle: ", path); + while (!bundle.end_of_data() && bundle.check_available(27)) + { + // TODO: remove this work-around for Botan 1.11.29 + // https://github.com/randombit/botan/issues/438#issuecomment-192866796 + // Note that every certificate that fails to be transcoded into latin-1 + // will be ignored. As a result, some TLS connection may be refused + // because the certificate is signed by an issuer that was ignored. + try { + Botan::X509_Certificate cert(bundle); + BasicCredentialsManager::certificate_store.add_certificate(std::move(cert)); + } catch (const Botan::Decoding_Error& error) { + continue; + } + } + // Only use the first file that can successfully be read. + return true; + } + catch (const Botan::Stream_IO_Error& e) + { + log_debug(e.what()); + } + } + return false; +} + +void BasicCredentialsManager::load_certs() +{ + // Only load the certificates the first time + if (BasicCredentialsManager::certs_loaded) + return; + const std::string conf_path = Config::get("ca_file", ""); + std::vector paths; + if (conf_path.empty()) + paths = default_cert_files; + else + paths.push_back(conf_path); + + if (BasicCredentialsManager::try_to_open_one_ca_bundle(paths)) + BasicCredentialsManager::certs_loaded = true; + else + log_warning("The CA could not be loaded, TLS negociation will probably fail."); +} + +std::vector BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&) +{ + return {&this->certificate_store}; +} + +#endif diff --git a/src/network/credentials_manager.hpp b/src/network/credentials_manager.hpp new file mode 100644 index 0000000..c463ad4 --- /dev/null +++ b/src/network/credentials_manager.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "biboumi.h" + +#ifdef BOTAN_FOUND + +#include +#include + +class TCPSocketHandler; + +/** + * If the given cert isn’t valid, based on the given hostname + * and fingerprint, then throws the exception if it’s non-empty. + * + * Must be called after the standard (from Botan) way of + * checking the certificate, if we want to also accept certificates based + * on a trusted fingerprint. + */ +void check_tls_certificate(const std::vector& certs, + const std::string& hostname, const std::string& trusted_fingerprint, + std::exception_ptr exc); + +class BasicCredentialsManager: public Botan::Credentials_Manager +{ +public: + BasicCredentialsManager(const TCPSocketHandler* const socket_handler); + + BasicCredentialsManager(BasicCredentialsManager&&) = delete; + BasicCredentialsManager(const BasicCredentialsManager&) = delete; + BasicCredentialsManager& operator=(const BasicCredentialsManager&) = delete; + BasicCredentialsManager& operator=(BasicCredentialsManager&&) = delete; + +#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) + void verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector&) override final; +#endif + std::vector trusted_certificate_authorities(const std::string& type, + const std::string& context) override final; + void set_trusted_fingerprint(const std::string& fingerprint); + const std::string& get_trusted_fingerprint() const; + +private: + const TCPSocketHandler* const socket_handler; + + static bool try_to_open_one_ca_bundle(const std::vector& paths); + static void load_certs(); + static Botan::Certificate_Store_In_Memory certificate_store; + static bool certs_loaded; + std::string trusted_fingerprint; +}; + +#endif //BOTAN_FOUND + diff --git a/src/network/dns_handler.cpp b/src/network/dns_handler.cpp new file mode 100644 index 0000000..7f0c96a --- /dev/null +++ b/src/network/dns_handler.cpp @@ -0,0 +1,46 @@ +#include +#ifdef UDNS_FOUND + +#include +#include +#include + +#include + +#include +#include +#include + +class Resolver; + +using namespace std::string_literals; + +std::unique_ptr DNSHandler::socket_handler{}; + +DNSHandler::DNSHandler(std::shared_ptr& poller) +{ + dns_init(nullptr, 0); + const auto socket = dns_open(nullptr); + if (socket == -1) + throw std::runtime_error("Failed to initialize udns socket: "s + strerror(errno)); + + DNSHandler::socket_handler = std::make_unique(poller, socket); +} + +void DNSHandler::destroy() +{ + DNSHandler::socket_handler.reset(nullptr); + dns_close(nullptr); +} + +void DNSHandler::watch() +{ + DNSHandler::socket_handler->watch(); +} + +void DNSHandler::unwatch() +{ + DNSHandler::socket_handler->unwatch(); +} + +#endif /* UDNS_FOUND */ diff --git a/src/network/dns_handler.hpp b/src/network/dns_handler.hpp new file mode 100644 index 0000000..c694452 --- /dev/null +++ b/src/network/dns_handler.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#ifdef UDNS_FOUND + +class Poller; + +#include + +#include +#include +#include + +class DNSHandler +{ +public: + explicit DNSHandler(std::shared_ptr& poller); + ~DNSHandler() = default; + + DNSHandler(const DNSHandler&) = delete; + DNSHandler(DNSHandler&&) = delete; + DNSHandler& operator=(const DNSHandler&) = delete; + DNSHandler& operator=(DNSHandler&&) = delete; + + void destroy(); + + static void watch(); + static void unwatch(); + +private: + /** + * Manager for the socket returned by udns, that we need to watch with the poller + */ + static std::unique_ptr socket_handler; +}; + +#endif /* UDNS_FOUND */ diff --git a/src/network/dns_socket_handler.cpp b/src/network/dns_socket_handler.cpp new file mode 100644 index 0000000..5c286c4 --- /dev/null +++ b/src/network/dns_socket_handler.cpp @@ -0,0 +1,43 @@ +#include +#ifdef UDNS_FOUND + +#include +#include +#include + +#include + +DNSSocketHandler::DNSSocketHandler(std::shared_ptr& poller, + const socket_t socket): + SocketHandler(poller, socket) +{ + poller->add_socket_handler(this); +} + +DNSSocketHandler::~DNSSocketHandler() +{ + this->unwatch(); +} + +void DNSSocketHandler::on_recv() +{ + dns_ioevent(nullptr, 0); +} + +bool DNSSocketHandler::is_connected() const +{ + return true; +} + +void DNSSocketHandler::unwatch() +{ + if (this->poller->is_managing_socket(this->socket)) + this->poller->remove_socket_handler(this->socket); +} + +void DNSSocketHandler::watch() +{ + this->poller->add_socket_handler(this); +} + +#endif /* UDNS_FOUND */ diff --git a/src/network/dns_socket_handler.hpp b/src/network/dns_socket_handler.hpp new file mode 100644 index 0000000..6e83e87 --- /dev/null +++ b/src/network/dns_socket_handler.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#ifdef UDNS_FOUND + +#include + +/** + * Manage the UDP socket provided by udns, we do not create, open or close the + * socket ourself: this is done by udns. We only watch it for readability + */ +class DNSSocketHandler: public SocketHandler +{ +public: + explicit DNSSocketHandler(std::shared_ptr& poller, const socket_t socket); + ~DNSSocketHandler(); + DNSSocketHandler(const DNSSocketHandler&) = delete; + DNSSocketHandler(DNSSocketHandler&&) = delete; + DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; + DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; + + void on_recv() override final; + + /** + * Always true, see the comment for connect() + */ + bool is_connected() const override final; + + void watch(); + void unwatch(); +}; + +#endif // UDNS_FOUND diff --git a/src/network/poller.cpp b/src/network/poller.cpp new file mode 100644 index 0000000..9f5bcfb --- /dev/null +++ b/src/network/poller.cpp @@ -0,0 +1,234 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +Poller::Poller() +{ +#if POLLER == POLL + this->nfds = 0; +#elif POLLER == EPOLL + this->epfd = ::epoll_create1(0); + if (this->epfd == -1) + { + log_error("epoll failed: ", strerror(errno)); + throw std::runtime_error("Could not create epoll instance"); + } +#endif +} + +Poller::~Poller() +{ +#if POLLER == EPOLL + if (this->epfd > 0) + ::close(this->epfd); +#endif +} + +void Poller::add_socket_handler(SocketHandler* socket_handler) +{ + // Don't do anything if the socket is already managed + const auto it = this->socket_handlers.find(socket_handler->get_socket()); + if (it != this->socket_handlers.end()) + return ; + + this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); + + // We always watch all sockets for receive events +#if POLLER == POLL + this->fds[this->nfds].fd = socket_handler->get_socket(); + this->fds[this->nfds].events = POLLIN; + this->nfds++; +#endif +#if POLLER == EPOLL + struct epoll_event event = {EPOLLIN, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: ", strerror(errno)); + throw std::runtime_error("Could not add socket to epoll"); + } +#endif +} + +void Poller::remove_socket_handler(const socket_t socket) +{ + const auto it = this->socket_handlers.find(socket); + if (it == this->socket_handlers.end()) + throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); + this->socket_handlers.erase(it); + +#if POLLER == POLL + for (size_t i = 0; i < this->nfds; i++) + { + if (this->fds[i].fd == socket) + { + // Move all subsequent pollfd by one on the left, erasing the + // value of the one we remove + for (size_t j = i; j < this->nfds - 1; ++j) + { + this->fds[j].fd = this->fds[j+1].fd; + this->fds[j].events= this->fds[j+1].events; + } + this->nfds--; + } + } +#elif POLLER == EPOLL + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); + if (res == -1) + { + log_error("epoll_ctl failed: ", strerror(errno)); + throw std::runtime_error("Could not remove socket from epoll"); + } +#endif +} + +void Poller::watch_send_events(SocketHandler* socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i < this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN|POLLOUT; + return; + } + } + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: ", strerror(errno)); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif +} + +void Poller::stop_watching_send_events(SocketHandler* socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i <= this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN; + return; + } + } + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event = {EPOLLIN, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: ", strerror(errno)); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif +} + +int Poller::poll(const std::chrono::milliseconds& timeout) +{ + if (this->socket_handlers.empty() && timeout == utils::no_timeout) + return -1; +#if POLLER == POLL + // Convert our nice timeout into this ugly struct + struct timespec timeout_ts; + struct timespec* timeout_tsp; + if (timeout > 0s) + { + auto seconds = std::chrono::duration_cast(timeout); + timeout_ts.tv_sec = seconds.count(); + timeout_ts.tv_nsec = std::chrono::duration_cast(timeout - seconds).count(); + timeout_tsp = &timeout_ts; + } + else + timeout_tsp = nullptr; + + // Unblock all signals, only during the ppoll call + sigset_t empty_signal_set; + sigemptyset(&empty_signal_set); + int nb_events = ::ppoll(this->fds, this->nfds, timeout_tsp, + &empty_signal_set); + if (nb_events < 0) + { + if (errno == EINTR) + return true; + log_error("poll failed: ", strerror(errno)); + throw std::runtime_error("Poll failed"); + } + // We cannot possibly have more ready events than the number of fds we are + // watching + assert(static_cast(nb_events) <= this->nfds); + for (size_t i = 0; i < this->nfds && nb_events != 0; ++i) + { + auto socket_handler = this->socket_handlers.at(this->fds[i].fd); + if (this->fds[i].revents == 0) + continue; + else if (this->fds[i].revents & POLLIN && socket_handler->is_connected()) + { + socket_handler->on_recv(); + nb_events--; + } + else if (this->fds[i].revents & POLLOUT && socket_handler->is_connected()) + { + socket_handler->on_send(); + nb_events--; + } + else if (this->fds[i].revents & POLLOUT || + this->fds[i].revents & POLLIN) + { + socket_handler->connect(); + nb_events--; + } + } + return 1; +#elif POLLER == EPOLL + static const size_t max_events = 12; + struct epoll_event revents[max_events]; + // Unblock all signals, only during the epoll_pwait call + sigset_t empty_signal_set; + sigemptyset(&empty_signal_set); + const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(), + &empty_signal_set); + if (nb_events == -1) + { + if (errno == EINTR) + return 0; + log_error("epoll wait: ", strerror(errno)); + throw std::runtime_error("Epoll_wait failed"); + } + for (int i = 0; i < nb_events; ++i) + { + auto socket_handler = static_cast(revents[i].data.ptr); + if (revents[i].events & EPOLLIN && socket_handler->is_connected()) + socket_handler->on_recv(); + else if (revents[i].events & EPOLLOUT && socket_handler->is_connected()) + socket_handler->on_send(); + else if (revents[i].events & EPOLLOUT) + socket_handler->connect(); + } + return nb_events; +#endif +} + +size_t Poller::size() const +{ + return this->socket_handlers.size(); +} + +bool Poller::is_managing_socket(const socket_t socket) const +{ + return (this->socket_handlers.find(socket) != this->socket_handlers.end()); +} diff --git a/src/network/poller.hpp b/src/network/poller.hpp new file mode 100644 index 0000000..3cc2710 --- /dev/null +++ b/src/network/poller.hpp @@ -0,0 +1,98 @@ +#pragma once + + +#include + +#include +#include +#include + +#define POLL 1 +#define EPOLL 2 +#define KQUEUE 3 +#include +#ifndef POLLER + #define POLLER POLL +#endif + +#if POLLER == POLL + #include + #define MAX_POLL_FD_NUMBER 4096 +#elif POLLER == EPOLL + #include +#else + #error Invalid POLLER value +#endif + +/** + * We pass some SocketHandlers to this Poller, which uses + * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, + * and call the callbacks when event occurs. + * + * TODO: support these pollers: + * - kqueue(2) + */ + +class Poller +{ +public: + explicit Poller(); + ~Poller(); + Poller(const Poller&) = delete; + Poller(Poller&&) = delete; + Poller& operator=(const Poller&) = delete; + Poller& operator=(Poller&&) = delete; + /** + * Add a SocketHandler to be monitored by this Poller. All receive events + * are always automatically watched. + */ + void add_socket_handler(SocketHandler* socket_handler); + /** + * Remove (and stop managing) a SocketHandler, designated by the given socket_t. + */ + void remove_socket_handler(const socket_t socket); + /** + * Signal the poller that he needs to watch for send events for the given + * SocketHandler. + */ + void watch_send_events(SocketHandler* socket_handler); + /** + * Signal the poller that he needs to stop watching for send events for + * this SocketHandler. + */ + void stop_watching_send_events(SocketHandler* socket_handler); + /** + * Wait for all watched events, and call the SocketHandlers' callbacks + * when one is ready. Returns if nothing happened before the provided + * timeout. If the timeout is 0, it waits forever. If there is no + * watched event, returns -1 immediately, ignoring the timeout value. + * Otherwise, returns the number of event handled. If 0 is returned this + * means that we were interrupted by a signal, or the timeout occured. + */ + int poll(const std::chrono::milliseconds& timeout); + /** + * Returns the number of SocketHandlers managed by the poller. + */ + size_t size() const; + /** + * Whether the given socket is managed by the poller + */ + bool is_managing_socket(const socket_t socket) const; + +private: + /** + * A "list" of all the SocketHandlers that we manage, indexed by socket, + * because that's what is returned by select/poll/etc when an event + * occures. + */ + std::unordered_map socket_handlers; + +#if POLLER == POLL + struct pollfd fds[MAX_POLL_FD_NUMBER]; + nfds_t nfds; +#elif POLLER == EPOLL + int epfd; +#endif +}; + + diff --git a/src/network/resolver.cpp b/src/network/resolver.cpp new file mode 100644 index 0000000..db7fb32 --- /dev/null +++ b/src/network/resolver.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#ifdef UDNS_FOUND +# include +#endif + +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +#ifdef UDNS_FOUND +static std::map dns_error_messages { + {DNS_E_TEMPFAIL, "Timeout while contacting DNS servers"}, + {DNS_E_PROTOCOL, "Misformatted DNS reply"}, + {DNS_E_NXDOMAIN, "Domain name not found"}, + {DNS_E_NOMEM, "Out of memory"}, + {DNS_E_BADQUERY, "Misformatted domain name"} +}; +#endif + +Resolver::Resolver(): +#ifdef UDNS_FOUND + resolved4(false), + resolved6(false), + resolving(false), + port{}, +#endif + resolved(false), + error_msg{} +{ +} + +void Resolver::resolve(const std::string& hostname, const std::string& port, + SuccessCallbackType success_cb, ErrorCallbackType error_cb) +{ + this->error_cb = error_cb; + this->success_cb = success_cb; +#ifdef UDNS_FOUND + this->port = port; +#endif + + this->start_resolving(hostname, port); +} + +int Resolver::call_getaddrinfo(const char *name, const char* port, int flags) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = flags; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + struct addrinfo* addr_res = nullptr; + const int res = ::getaddrinfo(name, port, + &hints, &addr_res); + + if (res == 0 && addr_res) + { + if (!this->addr) + this->addr.reset(addr_res); + else + { // Append this result at the end of the linked list + struct addrinfo *rp = this->addr.get(); + while (rp->ai_next) + rp = rp->ai_next; + rp->ai_next = addr_res; + } + } + + return res; +} + +#ifdef UDNS_FOUND +void Resolver::start_resolving(const std::string& hostname, const std::string& port) +{ + this->resolving = true; + this->resolved = false; + this->resolved4 = false; + this->resolved6 = false; + + this->error_msg.clear(); + this->addr.reset(nullptr); + + // We first try to use it as an IP address directly. We tell getaddrinfo + // to NOT use any DNS resolution. + if (this->call_getaddrinfo(hostname.data(), port.data(), AI_NUMERICHOST) == 0) + { + this->on_resolved(); + return; + } + + // Then we look into /etc/hosts to translate the given hostname + const auto hosts = this->look_in_etc_hosts(hostname); + if (!hosts.empty()) + { + for (const auto &host: hosts) + this->call_getaddrinfo(host.data(), port.data(), AI_NUMERICHOST); + this->on_resolved(); + return; + } + + // And finally, we try a DNS resolution + auto hostname6_resolved = [](dns_ctx*, dns_rr_a6* result, void* data) + { + Resolver* resolver = static_cast(data); + resolver->on_hostname6_resolved(result); + resolver->after_resolved(); + std::free(result); + }; + + auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data) + { + Resolver* resolver = static_cast(data); + resolver->on_hostname4_resolved(result); + resolver->after_resolved(); + std::free(result); + }; + + DNSHandler::watch(); + auto res = dns_submit_a4(nullptr, hostname.data(), 0, hostname4_resolved, this); + if (!res) + this->on_hostname4_resolved(nullptr); + res = dns_submit_a6(nullptr, hostname.data(), 0, hostname6_resolved, this); + if (!res) + this->on_hostname6_resolved(nullptr); + + this->start_timer(); +} + +void Resolver::start_timer() +{ + const auto timeout = dns_timeouts(nullptr, -1, 0); + if (timeout < 0) + return; + TimedEvent event(std::chrono::steady_clock::now() + std::chrono::seconds(timeout), [this]() { this->start_timer(); }, "DNS"); + TimedEventsManager::instance().add_event(std::move(event)); +} + +std::vector Resolver::look_in_etc_hosts(const std::string &hostname) +{ + std::ifstream hosts("/etc/hosts"); + std::string line; + + std::vector results; + while (std::getline(hosts, line)) + { + if (line.empty()) + continue; + + std::string ip; + std::istringstream line_stream(line); + line_stream >> ip; + if (ip.empty() || ip[0] == '#') + continue; + + std::string host; + while (line_stream >> host && !host.empty() && host[0] != '#') + { + if (hostname == host) + { + results.push_back(ip); + break; + } + } + } + return results; +} + +void Resolver::on_hostname4_resolved(dns_rr_a4 *result) +{ + this->resolved4 = true; + + const auto status = dns_status(nullptr); + + if (status >= 0 && result) + { + char buf[INET6_ADDRSTRLEN]; + + for (auto i = 0; i < result->dnsa4_nrr; ++i) + { + inet_ntop(AF_INET, &result->dnsa4_addr[i], buf, sizeof(buf)); + this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST); + } + } + else + { + const auto error = dns_error_messages.find(status); + if (error != end(dns_error_messages)) + this->error_msg = error->second; + } +} + +void Resolver::on_hostname6_resolved(dns_rr_a6 *result) +{ + this->resolved6 = true; + + const auto status = dns_status(nullptr); + + if (status >= 0 && result) + { + char buf[INET6_ADDRSTRLEN]; + for (auto i = 0; i < result->dnsa6_nrr; ++i) + { + inet_ntop(AF_INET6, &result->dnsa6_addr[i], buf, sizeof(buf)); + this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST); + } + } +} + +void Resolver::after_resolved() +{ + if (dns_active(nullptr) == 0) + DNSHandler::unwatch(); + + if (this->resolved6 && this->resolved4) + this->on_resolved(); +} + +void Resolver::on_resolved() +{ + this->resolved = true; + this->resolving = false; + if (!this->addr) + { + if (this->error_cb) + this->error_cb(this->error_msg.data()); + } + else + { + if (this->success_cb) + this->success_cb(this->addr.get()); + } +} + +#else // ifdef UDNS_FOUND + +void Resolver::start_resolving(const std::string& hostname, const std::string& port) +{ + // If the resolution fails, the addr will be unset + this->addr.reset(nullptr); + + const auto res = this->call_getaddrinfo(hostname.data(), port.data(), 0); + + this->resolved = true; + + if (res != 0) + { + this->error_msg = gai_strerror(res); + if (this->error_cb) + this->error_cb(this->error_msg.data()); + } + else + { + if (this->success_cb) + this->success_cb(this->addr.get()); + } +} +#endif // ifdef UDNS_FOUND + +std::string addr_to_string(const struct addrinfo* rp) +{ + char buf[INET6_ADDRSTRLEN]; + if (rp->ai_family == AF_INET) + return ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin_addr, + buf, sizeof(buf)); + else if (rp->ai_family == AF_INET6) + return ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin6_addr, + buf, sizeof(buf)); + return {}; +} diff --git a/src/network/resolver.hpp b/src/network/resolver.hpp new file mode 100644 index 0000000..f65ff86 --- /dev/null +++ b/src/network/resolver.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "biboumi.h" + +#include +#include +#include +#include + +#include +#include +#include +#ifdef UDNS_FOUND +# include +#endif + +class AddrinfoDeleter +{ + public: + void operator()(struct addrinfo* addr) + { + freeaddrinfo(addr); + } +}; + + +class Resolver +{ +public: + + using ErrorCallbackType = std::function; + using SuccessCallbackType = std::function; + + Resolver(); + ~Resolver() = default; + Resolver(const Resolver&) = delete; + Resolver(Resolver&&) = delete; + Resolver& operator=(const Resolver&) = delete; + Resolver& operator=(Resolver&&) = delete; + + bool is_resolving() const + { +#ifdef UDNS_FOUND + return this->resolving; +#else + return false; +#endif + } + + bool is_resolved() const + { + return this->resolved; + } + + const auto& get_result() const + { + return this->addr; + } + std::string get_error_message() const + { + return this->error_msg; + } + + void clear() + { +#ifdef UDNS_FOUND + this->resolved6 = false; + this->resolved4 = false; + this->resolving = false; + this->port.clear(); +#endif + this->resolved = false; + this->addr.reset(); + this->error_msg.clear(); + } + + void resolve(const std::string& hostname, const std::string& port, + SuccessCallbackType success_cb, ErrorCallbackType error_cb); + +private: + void start_resolving(const std::string& hostname, const std::string& port); + std::vector look_in_etc_hosts(const std::string& hostname); + /** + * Call getaddrinfo() on the given hostname or IP, and append the result + * to our internal addrinfo list. Return getaddrinfo()’s return value. + */ + int call_getaddrinfo(const char* name, const char* port, int flags); + +#ifdef UDNS_FOUND + void on_hostname4_resolved(dns_rr_a4 *result); + void on_hostname6_resolved(dns_rr_a6 *result); + /** + * Called after one record (4 or 6) has been resolved. + */ + void after_resolved(); + + void start_timer(); + + void on_resolved(); + + bool resolved4; + bool resolved6; + + bool resolving; + + std::string port; + +#endif + /** + * Tells if we finished the resolution process. It doesn't indicate if it + * was successful (it is true even if the result is an error). + */ + bool resolved; + std::string error_msg; + + std::unique_ptr addr; + + ErrorCallbackType error_cb; + SuccessCallbackType success_cb; +}; + +std::string addr_to_string(const struct addrinfo* rp); diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp new file mode 100644 index 0000000..181a6c0 --- /dev/null +++ b/src/network/socket_handler.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +class Poller; + +using socket_t = int; + +class SocketHandler +{ +public: + explicit SocketHandler(std::shared_ptr& poller, const socket_t socket): + poller(poller), + socket(socket) + {} + virtual ~SocketHandler() = default; + SocketHandler(const SocketHandler&) = delete; + SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; + + virtual void on_recv() {} + virtual void on_send() {} + virtual void connect() {} + virtual bool is_connected() const = 0; + + socket_t get_socket() const + { return this->socket; } + +protected: + /** + * A pointer to the poller that manages us, because we need to communicate + * with it. + */ + std::shared_ptr poller; + /** + * The handled socket. + */ + socket_t socket; +}; + diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp new file mode 100644 index 0000000..4628703 --- /dev/null +++ b/src/network/tcp_client_socket_handler.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std::string_literals; + +TCPClientSocketHandler::TCPClientSocketHandler(std::shared_ptr& poller): + TCPSocketHandler(poller), + hostname_resolution_failed(false), + connected(false), + connecting(false) +{} + +TCPClientSocketHandler::~TCPClientSocketHandler() +{ + this->close(); +} + +void TCPClientSocketHandler::init_socket(const struct addrinfo* rp) +{ + if (this->socket != -1) + ::close(this->socket); + if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) + throw std::runtime_error("Could not create socket: "s + std::strerror(errno)); + // Bind the socket to a specific address, if specified + if (!this->bind_addr.empty()) + { + // Convert the address from string format to a sockaddr that can be + // used in bind() + struct addrinfo* result; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + int err = ::getaddrinfo(this->bind_addr.data(), nullptr, &hints, &result); + if (err != 0 || !result) + log_error("Failed to bind socket to ", this->bind_addr, ": ", + gai_strerror(err)); + else + { + utils::ScopeGuard sg([result](){ freeaddrinfo(result); }); + struct addrinfo* rp; + for (rp = result; rp; rp = rp->ai_next) + { + if ((::bind(this->socket, + reinterpret_cast(rp->ai_addr), + rp->ai_addrlen)) == 0) + break; + } + if (!rp) + log_error("Failed to bind socket to ", this->bind_addr, ": ", + strerror(errno)); + else + log_info("Socket successfully bound to ", this->bind_addr); + } + } + int optval = 1; + if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) + log_warning("Failed to enable TCP keepalive on socket: ", strerror(errno)); + // Set the socket on non-blocking mode. This is useful to receive a EAGAIN + // error when connect() would block, to not block the whole process if a + // remote is not responsive. + const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); + if ((existing_flags == -1) || + (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) + throw std::runtime_error("Could not initialize socket: "s + std::strerror(errno)); +} + +void TCPClientSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) +{ + this->address = address; + this->port = port; + this->use_tls = tls; + + struct addrinfo* addr_res; + + if (!this->connecting) + { + // Get the addrinfo from getaddrinfo (or using udns), only if + // this is the first call of this function. + if (!this->resolver.is_resolved()) + { + log_info("Trying to connect to ", address, ":", port); + // Start the asynchronous process of resolving the hostname. Once + // the addresses have been found and `resolved` has been set to true + // (but connecting will still be false), TCPClientSocketHandler::connect() + // needs to be called, again. + this->resolver.resolve(address, port, + [this](const struct addrinfo*) + { + log_debug("Resolution success, calling connect() again"); + this->connect(); + }, + [this](const char*) + { + log_debug("Resolution failed, calling connect() again"); + this->connect(); + }); + return; + } + else + { + // The DNS resolver resolved the hostname and the available addresses + // where saved in the addrinfo linked list. Now, just use + // this list to try to connect. + addr_res = this->resolver.get_result().get(); + if (!addr_res) + { + this->hostname_resolution_failed = true; + const auto msg = this->resolver.get_error_message(); + this->close(); + this->on_connection_failed(msg); + return ; + } + } + } + else + { // This function is called again, use the saved addrinfo structure, + // instead of re-doing the whole getaddrinfo process. + addr_res = &this->addrinfo; + } + + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) + { + if (!this->connecting) + { + try { + this->init_socket(rp); + } + catch (const std::runtime_error& error) { + log_error("Failed to init socket: ", error.what()); + break; + } + } + + this->display_resolved_ip(rp); + + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 + || errno == EISCONN) + { + log_info("Connection success."); + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + this->poller->add_socket_handler(this); + this->connected = true; + this->connecting = false; +#ifdef BOTAN_FOUND + if (this->use_tls) + this->start_tls(this->address, this->port); +#endif + this->connection_date = std::chrono::system_clock::now(); + + // Get our local TCP port and store it + this->local_port = static_cast(-1); + if (rp->ai_family == AF_INET6) + { + struct sockaddr_in6 a; + socklen_t l = sizeof(a); + if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) + this->local_port = ntohs(a.sin6_port); + } + else if (rp->ai_family == AF_INET) + { + struct sockaddr_in a; + socklen_t l = sizeof(a); + if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) + this->local_port = ntohs(a.sin_port); + } + + log_debug("Local port: ", this->local_port, ", and remote port: ", this->port); + + this->on_connected(); + return ; + } + else if (errno == EINPROGRESS || errno == EALREADY) + { // retry this process later, when the socket + // is ready to be written on. + this->connecting = true; + this->poller->add_socket_handler(this); + this->poller->watch_send_events(this); + // Save the addrinfo structure, to use it on the next call + this->ai_addrlen = rp->ai_addrlen; + memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); + memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); + this->addrinfo.ai_addr = reinterpret_cast(&this->ai_addr); + this->addrinfo.ai_next = nullptr; + // If the connection has not succeeded or failed in 5s, we consider + // it to have failed + TimedEventsManager::instance().add_event( + TimedEvent(std::chrono::steady_clock::now() + 5s, + std::bind(&TCPClientSocketHandler::on_connection_timeout, this), + "connection_timeout"s + std::to_string(this->socket))); + return ; + } + log_info("Connection failed:", std::strerror(errno)); + } + log_error("All connection attempts failed."); + this->close(); + this->on_connection_failed(std::strerror(errno)); + return ; +} + +void TCPClientSocketHandler::on_connection_timeout() +{ + this->close(); + this->on_connection_failed("connection timed out"); +} + +void TCPClientSocketHandler::connect() +{ + this->connect(this->address, this->port, this->use_tls); +} + +void TCPClientSocketHandler::close() +{ + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + + TCPSocketHandler::close(); + + this->connected = false; + this->connecting = false; + this->port.clear(); + this->resolver.clear(); +} + +void TCPClientSocketHandler::display_resolved_ip(struct addrinfo* rp) const +{ + if (rp->ai_family == AF_INET) + log_debug("Trying IPv4 address ", addr_to_string(rp)); + else if (rp->ai_family == AF_INET6) + log_debug("Trying IPv6 address ", addr_to_string(rp)); +} + +bool TCPClientSocketHandler::is_connected() const +{ + return this->connected; +} + +bool TCPClientSocketHandler::is_connecting() const +{ + return this->connecting || this->resolver.is_resolving(); +} + +std::string TCPClientSocketHandler::get_port() const +{ + return this->port; +} + +bool TCPClientSocketHandler::match_port_pairt(const uint16_t local, const uint16_t remote) const +{ + const uint16_t remote_port = static_cast(std::stoi(this->port)); + return this->is_connected() && local == this->local_port && remote == remote_port; +} diff --git a/src/network/tcp_client_socket_handler.hpp b/src/network/tcp_client_socket_handler.hpp new file mode 100644 index 0000000..74caca9 --- /dev/null +++ b/src/network/tcp_client_socket_handler.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include + +class TCPClientSocketHandler: public TCPSocketHandler +{ + public: + TCPClientSocketHandler(std::shared_ptr& poller); + ~TCPClientSocketHandler(); + /** + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. + */ + void connect(const std::string& address, const std::string& port, const bool tls); + void connect() override final; + /** + * Called by a TimedEvent, when the connection did not succeed or fail + * after a given time. + */ + void on_connection_timeout(); + /** + * Called when the connection is successful. + */ + virtual void on_connected() = 0; + bool is_connected() const override; + bool is_connecting() const override; + + std::string get_port() const; + + void close() override final; + std::chrono::system_clock::time_point connection_date; + + /** + * Whether or not this connection is using the two given TCP ports. + */ + bool match_port_pairt(const uint16_t local, const uint16_t remote) const; + + protected: + bool hostname_resolution_failed; + /** + * Address to bind the socket to, before calling connect(). + * If empty, it’s equivalent to binding to INADDR_ANY. + */ + std::string bind_addr; + /** + * Display the resolved IP, just for information purpose. + */ + void display_resolved_ip(struct addrinfo* rp) const; + private: + /** + * Initialize the socket with the parameters contained in the given + * addrinfo structure. + */ + void init_socket(const struct addrinfo* rp); + /** + * DNS resolver + */ + Resolver resolver; + /** + * Keep the details of the addrinfo returned by the resolver that + * triggered a EINPROGRESS error when connect()ing to it, to reuse it + * directly when connect() is called again. + */ + struct addrinfo addrinfo{}; + struct sockaddr_in6 ai_addr{}; + socklen_t ai_addrlen{}; + + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + + uint16_t local_port{}; + + bool connected; + bool connecting; +}; diff --git a/src/network/tcp_server_socket.hpp b/src/network/tcp_server_socket.hpp new file mode 100644 index 0000000..c511962 --- /dev/null +++ b/src/network/tcp_server_socket.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +template +class TcpSocketServer: public SocketHandler +{ + public: + TcpSocketServer(std::shared_ptr& poller, const uint16_t port): + SocketHandler(poller, -1) + { + if ((this->socket = ::socket(AF_INET6, SOCK_STREAM, 0)) == -1) + throw std::runtime_error(std::string{"Could not create socket: "} + std::strerror(errno)); + + int opt = 1; + if (::setsockopt(this->socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) + throw std::runtime_error(std::string{"Failed to set socket option: "} + std::strerror(errno)); + + struct sockaddr_in6 addr{}; + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + addr.sin6_addr = IN6ADDR_ANY_INIT; + if ((::bind(this->socket, (const struct sockaddr*)&addr, sizeof(addr))) == -1) + { // If we can’t listen on this port, we just give up, but this is not fatal. + log_warning("Failed to bind on port ", std::to_string(port), ": ", std::strerror(errno)); + return; + } + + if ((::listen(this->socket, 10)) == -1) + throw std::runtime_error("listen() failed"); + + this->accept(); + } + ~TcpSocketServer() = default; + + void on_recv() override + { + // Accept a RemoteSocketType + int socket = ::accept(this->socket, nullptr, nullptr); + + auto client = std::make_unique(poller, socket, *this); + this->poller->add_socket_handler(client.get()); + this->sockets.push_back(std::move(client)); + } + + protected: + std::vector> sockets; + + private: + void accept() + { + this->poller->add_socket_handler(this); + } + bool is_connected() const override + { + return true; + } +}; diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp new file mode 100644 index 0000000..7eebae0 --- /dev/null +++ b/src/network/tcp_socket_handler.cpp @@ -0,0 +1,358 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOTAN_FOUND +# include +# include + +namespace +{ + Botan::AutoSeeded_RNG& get_rng() + { + static Botan::AutoSeeded_RNG rng{}; + return rng; + } + BiboumiTLSPolicy& get_policy() + { + static BiboumiTLSPolicy policy{}; + return policy; + } + Botan::TLS::Session_Manager_In_Memory& get_session_manager() + { + static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; + return session_manager; + } +} +#endif + +#ifndef UIO_FASTIOV +# define UIO_FASTIOV 8 +#endif + +using namespace std::string_literals; +using namespace std::chrono_literals; + +namespace ph = std::placeholders; + +TCPSocketHandler::TCPSocketHandler(std::shared_ptr& poller): + SocketHandler(poller, -1), + use_tls(false) +#ifdef BOTAN_FOUND + ,credential_manager(this) +#endif +{} + +TCPSocketHandler::~TCPSocketHandler() +{ + if (this->poller->is_managing_socket(this->get_socket())) + this->poller->remove_socket_handler(this->get_socket()); + if (this->socket != -1) + { + ::close(this->socket); + this->socket = -1; + } +} + +void TCPSocketHandler::on_recv() +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_recv(); + else +#endif + this->plain_recv(); +} + +void TCPSocketHandler::plain_recv() +{ + static constexpr size_t buf_size = 4096; + char buf[buf_size]; + void* recv_buf = this->get_receive_buffer(buf_size); + + if (recv_buf == nullptr) + recv_buf = buf; + + const ssize_t size = this->do_recv(recv_buf, buf_size); + + if (size > 0) + { + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); + } +} + +ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) +{ + ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); + if (0 == size) + { + this->on_connection_close(""); + this->close(); + } + else if (-1 == size) + { + if (this->is_connecting()) + log_warning("Error connecting: ", strerror(errno)); + else + log_warning("Error while reading from socket: ", strerror(errno)); + // Remember if we were connecting, or already connected when this + // happened, because close() sets this->connecting to false + const auto were_connecting = this->is_connecting(); + this->close(); + if (were_connecting) + this->on_connection_failed(strerror(errno)); + else + this->on_connection_close(strerror(errno)); + } + return size; +} + +void TCPSocketHandler::on_send() +{ + struct iovec msg_iov[UIO_FASTIOV] = {}; + struct msghdr msg{nullptr, 0, + msg_iov, + 0, nullptr, 0, 0}; + for (const std::string& s: this->out_buf) + { + // unconsting the content of s is ok, sendmsg will never modify it + msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); + msg_iov[msg.msg_iovlen].iov_len = s.size(); + msg.msg_iovlen++; + if (msg.msg_iovlen == UIO_FASTIOV) + break; + } + ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); + if (res < 0) + { + log_error("sendmsg failed: ", strerror(errno)); + this->on_connection_close(strerror(errno)); + this->close(); + } + else + { + // remove all the strings that were successfully sent. + auto it = this->out_buf.begin(); + while (it != this->out_buf.end()) + { + if (static_cast(res) >= it->size()) + { + res -= it->size(); + ++it; + } + else + { + // If one string has partially been sent, we use substr to + // crop it + if (res > 0) + *it = it->substr(res, std::string::npos); + break; + } + } + this->out_buf.erase(this->out_buf.begin(), it); + if (this->out_buf.empty()) + this->poller->stop_watching_send_events(this); + } +} + +void TCPSocketHandler::close() +{ + if (this->is_connected() || this->is_connecting()) + this->poller->remove_socket_handler(this->get_socket()); + if (this->socket != -1) + { + ::close(this->socket); + this->socket = -1; + } + this->in_buf.clear(); + this->out_buf.clear(); +} + +void TCPSocketHandler::send_data(std::string&& data) +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + try { + this->tls_send(std::move(data)); + } catch (const Botan::TLS::TLS_Exception& e) { + this->on_connection_close("TLS error: "s + e.what()); + this->close(); + return ; + } + else +#endif + this->raw_send(std::move(data)); +} + +void TCPSocketHandler::raw_send(std::string&& data) +{ + if (data.empty()) + return ; + this->out_buf.emplace_back(std::move(data)); + if (this->is_connected()) + this->poller->watch_send_events(this); +} + +void TCPSocketHandler::send_pending_data() +{ + if (this->is_connected() && !this->out_buf.empty()) + this->poller->watch_send_events(this); +} + +bool TCPSocketHandler::is_using_tls() const +{ + return this->use_tls; +} + +void* TCPSocketHandler::get_receive_buffer(const size_t) const +{ + return nullptr; +} + +void TCPSocketHandler::consume_in_buffer(const std::size_t size) +{ + this->in_buf = this->in_buf.substr(size, std::string::npos); +} + +#ifdef BOTAN_FOUND +void TCPSocketHandler::start_tls(const std::string& address, const std::string& port) +{ + Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port)); + this->tls = std::make_unique( +# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) + *this, +# else + [this](const Botan::byte* data, size_t size) { this->tls_emit_data(data, size); }, + [this](const Botan::byte* data, size_t size) { this->tls_record_received(0, data, size); }, + [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); }, + [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); }, +# endif + get_session_manager(), this->credential_manager, get_policy(), + get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version()); +} + +void TCPSocketHandler::tls_recv() +{ + static constexpr size_t buf_size = 4096; + Botan::byte recv_buf[buf_size]; + + const ssize_t size = this->do_recv(recv_buf, buf_size); + if (size > 0) + { + const bool was_active = this->tls->is_active(); + try { + this->tls->received_data(recv_buf, static_cast(size)); + } catch (const Botan::TLS::TLS_Exception& e) { + // May happen if the server sends malformed TLS data (buggy server, + // or more probably we are just connected to a server that sends + // plain-text) + this->on_connection_close("TLS error: "s + e.what()); + this->close(); + return ; + } + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } +} + +void TCPSocketHandler::tls_send(std::string&& data) +{ + // We may not be connected yet, or the tls session has + // not yet been negociated + if (this->tls && this->tls->is_active()) + { + const bool was_active = this->tls->is_active(); + if (!this->pre_buf.empty()) + { + this->tls->send(this->pre_buf.data(), this->pre_buf.size()); + this->pre_buf.clear(); + } + if (!data.empty()) + this->tls->send(reinterpret_cast(data.data()), + data.size()); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } + else + this->pre_buf.insert(this->pre_buf.end(), + std::make_move_iterator(data.begin()), + std::make_move_iterator(data.end())); +} + +void TCPSocketHandler::tls_record_received(uint64_t, const Botan::byte *data, size_t size) +{ + this->in_buf += std::string(reinterpret_cast(data), + size); + if (!this->in_buf.empty()) + this->parse_in_buffer(size); +} + +void TCPSocketHandler::tls_emit_data(const Botan::byte *data, size_t size) +{ + this->raw_send(std::string(reinterpret_cast(data), size)); +} + +void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert) +{ + log_debug("tls_alert: ", alert.type_string()); +} + +bool TCPSocketHandler::tls_session_established(const Botan::TLS::Session& session) +{ + log_debug("Handshake with ", session.server_info().hostname(), " complete.", + " Version: ", session.version().to_string(), + " using ", session.ciphersuite().to_string()); + if (!session.session_id().empty()) + log_debug("Session ID ", Botan::hex_encode(session.session_id())); + if (!session.session_ticket().empty()) + log_debug("Session ticket ", Botan::hex_encode(session.session_ticket())); + return true; +} + +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) +void TCPSocketHandler::tls_verify_cert_chain(const std::vector& cert_chain, + const std::vector>& ocsp_responses, + const std::vector& trusted_roots, + Botan::Usage_Type usage, const std::string& hostname, + const Botan::TLS::Policy& policy) +{ + log_debug("Checking remote certificate for hostname ", hostname); + try + { + Botan::TLS::Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); + log_debug("Certificate is valid"); + } + catch (const std::exception& tls_exception) + { + log_warning("TLS certificate check failed: ", tls_exception.what()); + std::exception_ptr exception_ptr{}; + if (this->abort_on_invalid_cert()) + exception_ptr = std::current_exception(); + + check_tls_certificate(cert_chain, hostname, this->credential_manager.get_trusted_fingerprint(), exception_ptr); + } +} +#endif + +void TCPSocketHandler::on_tls_activated() +{ + this->send_data({}); +} + +#endif // BOTAN_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp new file mode 100644 index 0000000..ba23861 --- /dev/null +++ b/src/network/tcp_socket_handler.hpp @@ -0,0 +1,251 @@ +#pragma once + +#include "biboumi.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef BOTAN_FOUND + +# include +# include +# include + +class BiboumiTLSPolicy: public Botan::TLS::Policy +{ +public: +# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,33) + bool use_ecc_point_compression() const override + { + return true; + } + bool require_cert_revocation_info() const override + { + return false; + } +# endif +}; + +# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) +# define BOTAN_TLS_CALLBACKS_OVERRIDE override final +# else +# define BOTAN_TLS_CALLBACKS_OVERRIDE +# endif +#endif + +/** + * Does all the read/write, buffering etc. With optional tls. + * But doesn’t do any connect() or accept() or anything else. + */ +class TCPSocketHandler: public SocketHandler +#ifdef BOTAN_FOUND +# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) + ,public Botan::TLS::Callbacks +# endif +#endif +{ +protected: + ~TCPSocketHandler(); +public: + explicit TCPSocketHandler(std::shared_ptr& poller); + TCPSocketHandler(const TCPSocketHandler&) = delete; + TCPSocketHandler(TCPSocketHandler&&) = delete; + TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; + TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + + /** + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() + */ + void on_recv() override final; + /** + * Write as much data from out_buf as possible, in the socket. + */ + void on_send() override final; + /** + * Add the given data to out_buf and tell our poller that we want to be + * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. + */ + void send_data(std::string&& data); + /** + * Watch the socket for send events, if our out buffer is not empty. + */ + void send_pending_data(); + /** + * Close the connection, remove us from the poller + */ + virtual void close(); + /** + * Handle/consume (some of) the data received so far. The data to handle + * may be in the in_buf buffer, or somewhere else, depending on what + * get_receive_buffer() returned. If some data is used from in_buf, it + * should be truncated, only the unused data should be left untouched. + * + * The size argument is the size of the last chunk of data that was added to the buffer. + * + * The function should call consume_in_buffer, with the size that was consumed by the + * “parsing”, and thus to be removed from the input buffer. + */ + virtual void parse_in_buffer(const size_t size) = 0; +#ifdef BOTAN_FOUND + /** + * Tell whether the credential manager should cancel the connection when the + * certificate is invalid. + */ + virtual bool abort_on_invalid_cert() const + { + return true; + } +#endif + bool is_using_tls() const; + +private: + /** + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. + */ + ssize_t do_recv(void* recv_buf, const size_t buf_size); + /** + * Reads data from the socket and calls parse_in_buffer with it. + */ + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + + protected: + virtual bool is_connecting() const = 0; +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(const std::string& address, const std::string& port); + private: + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_record_received(uint64_t rec_no, const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_emit_data(const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert(Botan::TLS::Alert alert) BOTAN_TLS_CALLBACKS_OVERRIDE; + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_session_established(const Botan::TLS::Session& session) BOTAN_TLS_CALLBACKS_OVERRIDE; + +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) + void tls_verify_cert_chain(const std::vector& cert_chain, + const std::vector>& ocsp_responses, + const std::vector& trusted_roots, + Botan::Usage_Type usage, + const std::string& hostname, + const Botan::TLS::Policy& policy) BOTAN_TLS_CALLBACKS_OVERRIDE; +#endif + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * Where data is added, when we want to send something to the client. + */ + std::vector out_buf; +protected: + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Remove the given “size” first bytes from our in_buf. + */ + void consume_in_buffer(const std::size_t size); + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; + /** + * Called when we detect a disconnection from the remote host. + */ + virtual void on_connection_close(const std::string&) {} + virtual void on_connection_failed(const std::string&) {} + +#ifdef BOTAN_FOUND +protected: + BasicCredentialsManager credential_manager; +private: + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message and + * calls the output_fn callback with it as soon as it is created. + * Therefore, we do not want to create it if we do not intend to send any + * TLS-encrypted message. We create the object only when needed (for + * example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::vector pre_buf; +#endif // BOTAN_FOUND +}; diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp new file mode 100644 index 0000000..aa91dac --- /dev/null +++ b/src/utils/encoding.cpp @@ -0,0 +1,254 @@ +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +/** + * The UTF-8-encoded character used as a place holder when a character conversion fails. + * This is U+FFFD � "replacement character" + */ +static const char* invalid_char = "\xef\xbf\xbd"; +static const size_t invalid_char_len = 3; + +namespace utils +{ + /** + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + std::size_t get_next_codepoint_size(const unsigned char c) + { + if ((c & 0b11111000) == 0b11110000) // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return 4; + else if ((c & 0b11110000) == 0b11100000) // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + return 3; + else if ((c & 0b11100000) == 0b11000000) // 2 bytes: 110xxxxx 10xxxxxx + return 2; + return 1; // 1 byte: 0xxxxxxx + } + + bool is_valid_utf8(const char* s) + { + if (!s) + return false; + + const unsigned char* str = reinterpret_cast(s); + + while (*str) + { + const auto codepoint_size = get_next_codepoint_size(str[0]); + if (codepoint_size == 4) + { + if (!str[1] || !str[2] || !str[3] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000) + || ((str[3] & 0b11000000) != 0b10000000)) + return false; + } + else if (codepoint_size == 3) + { + if (!str[1] || !str[2] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000)) + return false; + } + else if (codepoint_size == 2) + { + if (!str[1] || + ((str[1] & 0b11000000) != 0b10000000)) + return false; + } + else if ((str[0] & 0b10000000) != 0) + return false; + str += codepoint_size; + } + return true; + } + + std::string remove_invalid_xml_chars(const std::string& original) + { + // The given string MUST be a valid utf-8 string + std::vector res(original.size(), '\0'); + + // pointer where we write valid chars + char* r = res.data(); + + const char* str = original.c_str(); + std::bitset<20> codepoint; + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 0b11111000) == 0b11110000) + { + codepoint = ((str[0] & 0b00000111) << 18); + codepoint |= ((str[1] & 0b00111111) << 12); + codepoint |= ((str[2] & 0b00111111) << 6 ); + codepoint |= ((str[3] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0x10FFFF) + { + ::memcpy(r, str, 4); + r += 4; + } + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 0b11110000) == 0b11100000) + { + codepoint = ((str[0] & 0b00001111) << 12); + codepoint |= ((str[1] & 0b00111111) << 6); + codepoint |= ((str[2] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0xD7FF || + (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) + { + ::memcpy(r, str, 3); + r += 3; + } + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 0b11100000) == 0b11000000) + { + // All 2 bytes char are valid, don't even bother calculating + // the codepoint + ::memcpy(r, str, 2); + r += 2; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 0b10000000) == 0) + { + codepoint = ((str[0] & 0b01111111)); + if (codepoint.to_ulong() == 0x09 || + codepoint.to_ulong() == 0x0A || + codepoint.to_ulong() == 0x0D || + codepoint.to_ulong() >= 0x20) + { + ::memcpy(r, str, 1); + r += 1; + } + str += 1; + } + else + throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); + } + return {res.data(), static_cast(r - res.data())}; + } + + std::string convert_to_utf8(const std::string& str, const char* charset) + { + std::string res; + + const iconv_t cd = iconv_open("UTF-8", charset); + if (cd == (iconv_t)-1) + throw std::runtime_error("Cannot convert into UTF-8"); + + // Make sure cd is always closed when we leave this function + const auto sg = utils::make_scope_guard([&cd](){ iconv_close(cd); }); + + size_t inbytesleft = str.size(); + + // iconv will not attempt to modify this buffer, but some plateform + // require a char** anyway +#ifdef ICONV_SECOND_ARGUMENT_IS_CONST + const char* inbuf_ptr = str.c_str(); +#else + char* inbuf_ptr = const_cast(str.c_str()); +#endif + + size_t outbytesleft = str.size() * 4; + char* outbuf = new char[outbytesleft]; + char* outbuf_ptr = outbuf; + + // Make sure outbuf is always deleted when we leave this function + const auto sg2 = utils::make_scope_guard([outbuf](){ delete[] outbuf; }); + + bool done = false; + while (done == false) + { + size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); + if ((size_t)-1 == error) + { + switch (errno) + { + case EILSEQ: + // Invalid byte found. Insert a placeholder instead of the + // converted character, jump one byte and continue + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + inbytesleft--; + inbuf_ptr++; + break; + case EINVAL: + // A multibyte sequence is not terminated, but we can't + // provide any more data, so we just add a placeholder to + // indicate that the character is not properly converted, + // and we stop the conversion + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + outbuf_ptr++; + done = true; + break; + case E2BIG: // This should never happen + default: // This should happen even neverer + done = true; + break; + } + } + else + { + // The conversion finished without any error, stop converting + done = true; + } + } + // Terminate the converted buffer, and copy that buffer it into the + // string we return + *outbuf_ptr = '\0'; + res = outbuf; + return res; + } + +} + +namespace xep0106 +{ + static const std::map encode_map = { + {' ', "\\20"}, + {'"', "\\22"}, + {'&', "\\26"}, + {'\'',"\\27"}, + {'/', "\\2f"}, + {':', "\\3a"}, + {'<', "\\3c"}, + {'>', "\\3e"}, + {'@', "\\40"}, + }; + + void decode(std::string& s) + { + std::string::size_type pos; + for (const auto& pair: encode_map) + while ((pos = s.find(pair.second)) != std::string::npos) + s.replace(pos, pair.second.size(), + 1, pair.first); + } + + void encode(std::string& s) + { + std::string::size_type pos; + while ((pos = s.find_first_of(" \"&'/:<>@")) != std::string::npos) + { + auto it = encode_map.find(s[pos]); + assert(it != encode_map.end()); + s.replace(pos, 1, it->second); + } + } +} diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp new file mode 100644 index 0000000..586edd8 --- /dev/null +++ b/src/utils/encoding.hpp @@ -0,0 +1,43 @@ +#pragma once + + +#include + +namespace utils +{ + /** + * Return the size, in bytes, of the next UTF-8 codepoint, based on + * the given char. + */ + std::size_t get_next_codepoint_size(const unsigned char c); + /** + * Returns true if the given null-terminated string is valid utf-8. + * + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s); + /** + * Remove all invalid codepoints from the given utf-8-encoded string. + * The value returned is a copy of the string, without the removed chars. + * + * See http://www.w3.org/TR/xml/#charsets for the list of valid characters + * in XML. + */ + std::string remove_invalid_xml_chars(const std::string& original); + /** + * Convert the given string (encoded is "encoding") into valid utf-8. + * If some decoding fails, insert an utf-8 placeholder character instead. + */ + std::string convert_to_utf8(const std::string& str, const char* encoding); +} + +namespace xep0106 +{ + /** + * Decode and encode inplace. + */ + void decode(std::string&); + void encode(std::string&); +} + + diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/src/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/src/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template +T get_first_non_empty(T&& last) +{ + return last; +} + +template +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward(args)...); +} diff --git a/src/utils/revstr.cpp b/src/utils/revstr.cpp new file mode 100644 index 0000000..87fd801 --- /dev/null +++ b/src/utils/revstr.cpp @@ -0,0 +1,9 @@ +#include + +namespace utils +{ + std::string revstr(const std::string& original) + { + return {original.rbegin(), original.rend()}; + } +} diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp new file mode 100644 index 0000000..8e521ea --- /dev/null +++ b/src/utils/revstr.hpp @@ -0,0 +1,11 @@ +#pragma once + + +#include + +namespace utils +{ + std::string revstr(const std::string& original); +} + + diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp new file mode 100644 index 0000000..e697fc3 --- /dev/null +++ b/src/utils/scopeguard.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +/** + * A class to be used to make sure some functions are called when the scope + * is left, because they will be called in the ScopeGuard's destructor. It + * can for example be used to delete some pointer whenever any exception is + * called. Example: + + * { + * ScopeGuard scope; + * int* number = new int(2); + * scope.add_callback([number]() { delete number; }); + * // Do some other stuff with the number. But these stuff might throw an exception: + * throw std::runtime_error("Some error not caught here, but in our caller"); + * return true; + * } + + * In this example, our pointer will always be deleted, even when the + * exception is thrown. If we want the functions to be called only when the + * scope is left because of an unexpected exception, we can use + * ScopeGuard::disable(); + */ + +namespace utils +{ + +class ScopeGuard +{ +public: + /** + * The constructor can take a callback. But additional callbacks can be + * added later with add_callback() + */ + explicit ScopeGuard(std::function&& func): + enabled(true) + { + this->add_callback(std::move(func)); + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + + /** + * default constructor, the scope guard is enabled but empty, use + * add_callback() + */ + explicit ScopeGuard(): + enabled(true) + { + } + /** + * Call all callbacks in the desctructor, unless it has been disabled. + */ + ~ScopeGuard() + { + if (this->enabled) + for (auto& func: this->callbacks) + func(); + } + /** + * Add a callback to be called in our destructor, one scope guard can be + * used for more than one task, if needed. + */ + void add_callback(std::function&& func) + { + this->callbacks.emplace_back(std::move(func)); + } + /** + * Disable that scope guard, nothing will be done when the scope is + * exited. + */ + void disable() + { + this->enabled = false; + } + +private: + bool enabled; + std::vector> callbacks; + +}; + +template +auto make_scope_guard(F f) +{ + static struct Empty {} empty; + auto deleter = [f = std::move(f)](Empty*) { f(); }; + return std::unique_ptr{&empty, std::move(deleter)}; +} + +} + diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp new file mode 100644 index 0000000..b77446e --- /dev/null +++ b/src/utils/sha1.cpp @@ -0,0 +1,32 @@ +#include + +#include + +#ifdef BOTAN_FOUND +# include +# include +#endif +#ifdef GCRYPT_FOUND +# include +# include +# include +# include +#endif + +std::string sha1(const std::string& input) +{ +#ifdef BOTAN_FOUND + auto sha1 = Botan::HashFunction::create_or_throw("SHA-1"); + sha1->update(input); + return Botan::hex_encode(sha1->final(), false); +#endif +#ifdef GCRYPT_FOUND + const auto hash_length = gcry_md_get_algo_dlen(GCRY_MD_SHA1); + std::vector output(hash_length, {}); + gcry_md_hash_buffer(GCRY_MD_SHA1, output.data(), input.data(), input.size()); + std::ostringstream digest; + for (std::size_t i = 0; i < hash_length; i++) + digest << std::hex << std::setfill('0') << std::setw(2) << static_cast(output[i]); + return digest.str(); +#endif +} diff --git a/src/utils/sha1.hpp b/src/utils/sha1.hpp new file mode 100644 index 0000000..6c551ac --- /dev/null +++ b/src/utils/sha1.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string sha1(const std::string& input); diff --git a/src/utils/split.cpp b/src/utils/split.cpp new file mode 100644 index 0000000..80f8dae --- /dev/null +++ b/src/utils/split.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace utils +{ + std::vector split(const std::string& s, const char delim, const bool allow_empty) + { + std::vector ret; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + { + if (item.empty() && !allow_empty) + continue ; + ret.emplace_back(std::move(item)); + } + return ret; + } +} diff --git a/src/utils/split.hpp b/src/utils/split.hpp new file mode 100644 index 0000000..3755ef8 --- /dev/null +++ b/src/utils/split.hpp @@ -0,0 +1,12 @@ +#pragma once + + +#include +#include + +namespace utils +{ + std::vector split(const std::string &s, const char delim, const bool allow_empty=true); +} + + diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..635e71a --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,28 @@ +#include +#include + +bool to_bool(const std::string& val) +{ + return (val == "1" || val == "true"); +} + +std::vector cut(const std::string& val, const std::size_t size) +{ + std::vector res; + std::string::size_type pos = 0; + while (pos < val.size()) + { + // Get the number of chars, <= size, that contain only whole + // UTF-8 codepoints. + std::size_t s = 0; + auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + while (s + codepoint_size <= size && pos + s < val.size()) + { + s += codepoint_size; + codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + } + res.emplace_back(val.substr(pos, s)); + pos += s; + } + return res; +} diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 0000000..84ba101 --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,10 @@ +#pragma once + + +#include +#include + +bool to_bool(const std::string& val); +std::vector cut(const std::string& val, const std::size_t size); + + diff --git a/src/utils/system.cpp b/src/utils/system.cpp new file mode 100644 index 0000000..c0bee11 --- /dev/null +++ b/src/utils/system.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +using namespace std::string_literals; + +namespace utils +{ +std::string get_system_name() +{ + struct utsname uts; + const int res = ::uname(&uts); + if (res == -1) + { + log_error("uname failed: ", std::strerror(errno)); + return "Unknown"; + } + return uts.sysname + " "s + uts.release; +} +} \ No newline at end of file diff --git a/src/utils/system.hpp b/src/utils/system.hpp new file mode 100644 index 0000000..7ea1677 --- /dev/null +++ b/src/utils/system.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace utils +{ +std::string get_system_name(); +} \ No newline at end of file diff --git a/src/utils/time.cpp b/src/utils/time.cpp new file mode 100644 index 0000000..8fa3fcd --- /dev/null +++ b/src/utils/time.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include +#include +#include + +#include "biboumi.h" + +namespace utils +{ +std::string to_string(const std::time_t& timestamp) +{ + constexpr std::size_t stamp_size = 21; + char date_buf[stamp_size]; + if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)) != stamp_size - 1) + return ""; + return {std::begin(date_buf), std::end(date_buf) - 1}; +} + +std::time_t parse_datetime(const std::string& stamp) +{ + static const char* format = "%Y-%m-%dT%H:%M:%S"; + std::tm t = {}; +#ifdef HAS_GET_TIME + std::istringstream ss(stamp); + ss.imbue(std::locale("C")); + + std::string timezone; + ss >> std::get_time(&t, format) >> timezone; + if (ss.fail()) + return -1; +#else + /* Y - m - d T H : M : S */ + constexpr std::size_t stamp_size_without_tz = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + if (!strptime(stamp.data(), format, &t)) { + return -1; + } + const std::string timezone(stamp.data() + stamp_size_without_tz); +#endif + + if (timezone.empty()) + return -1; + + if (timezone.compare(0, 1, "Z") != 0) + { + std::stringstream tz_ss; + tz_ss << timezone; + int multiplier = -1; + char prefix; + int hours; + char sep; + int minutes; + tz_ss >> prefix >> hours >> sep >> minutes; + if (tz_ss.fail()) + return -1; + if (prefix == '-') + multiplier = +1; + else if (prefix != '+') + return -1; + + t.tm_hour += multiplier * hours; + t.tm_min += multiplier * minutes; + } + return ::timegm(&t); +} + +} + + diff --git a/src/utils/time.hpp b/src/utils/time.hpp new file mode 100644 index 0000000..c71cd9c --- /dev/null +++ b/src/utils/time.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace utils +{ +std::string to_string(const std::time_t& timestamp); +std::time_t parse_datetime(const std::string& stamp); +} \ No newline at end of file diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp new file mode 100644 index 0000000..5077199 --- /dev/null +++ b/src/utils/timed_events.cpp @@ -0,0 +1,47 @@ +#include + +TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name): + time_point(std::move(time_point)), + callback(callback), + repeat(false), + repeat_delay(0), + name(name) +{ +} + +TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name): + time_point(std::chrono::steady_clock::now() + duration), + callback(callback), + repeat(true), + repeat_delay(std::move(duration)), + name(name) +{ +} + +bool TimedEvent::is_after(const TimedEvent& other) const +{ + return this->is_after(other.time_point); +} + +bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const +{ + return this->time_point > time_point; +} + +std::chrono::milliseconds TimedEvent::get_timeout() const +{ + auto diff = std::chrono::duration_cast(this->time_point - std::chrono::steady_clock::now()); + return std::max(diff, 0ms); +} + +void TimedEvent::execute() const +{ + this->callback(); +} + +const std::string& TimedEvent::get_name() const +{ + return this->name; +} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp new file mode 100644 index 0000000..6e28206 --- /dev/null +++ b/src/utils/timed_events.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace utils { +static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); +} + +class TimedEventsManager; + +/** + * A callback with an associated date. + */ + +class TimedEvent +{ + friend class TimedEventsManager; +public: + /** + * An event the occurs only once, at the given time_point + */ + explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name=""); + explicit TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name=""); + + explicit TimedEvent(TimedEvent&&) = default; + TimedEvent& operator=(TimedEvent&&) = default; + ~TimedEvent() = default; + + TimedEvent(const TimedEvent&) = delete; + TimedEvent& operator=(const TimedEvent&) = delete; + + /** + * Whether or not this event happens after the other one. + */ + bool is_after(const TimedEvent& other) const; + bool is_after(const std::chrono::steady_clock::time_point& time_point) const; + /** + * Return the duration difference between now and the event time point. + * If the difference would be negative (i.e. the event is expired), the + * returned value is 0 instead. The value cannot then be negative. + */ + std::chrono::milliseconds get_timeout() const; + void execute() const; + const std::string& get_name() const; + +private: + /** + * The next time point at which the event is executed. + */ + std::chrono::steady_clock::time_point time_point; + /** + * The function to execute. + */ + std::function callback; + /** + * Whether or not this events repeats itself until it is destroyed. + */ + bool repeat; + /** + * This value is added to the time_point each time the event is executed, + * if repeat is true. Otherwise it is ignored. + */ + std::chrono::milliseconds repeat_delay; + /** + * A name that is used to identify that event. If you want to find your + * event (for example if you want to cancel it), the name should be + * unique. + */ + std::string name; +}; + +/** + * A class managing a list of TimedEvents. + * They are sorted, new events can be added, removed, fetch, etc. + */ + +class TimedEventsManager +{ +public: + ~TimedEventsManager() = default; + + TimedEventsManager(const TimedEventsManager&) = delete; + TimedEventsManager(TimedEventsManager&&) = delete; + TimedEventsManager& operator=(const TimedEventsManager&) = delete; + TimedEventsManager& operator=(TimedEventsManager&&) = delete; + + /** + * Return the unique instance of this class + */ + static TimedEventsManager& instance(); + /** + * Add an event to the list of managed events. The list is sorted after + * this call. + */ + void add_event(TimedEvent&& event); + /** + * Returns the duration, in milliseconds, between now and the next + * available event. If the event is already expired (the duration is + * negative), 0 is returned instead (as in “it's not too late, execute it + * now”) + * Returns a negative value if no event is available. + */ + std::chrono::milliseconds get_timeout() const; + /** + * Execute all the expired events (if their expiration time is exactly + * now, or before now). The event is then removed from the list. If the + * event does repeat, its expiration time is updated and it is reinserted + * in the list at the correct position. + * Returns the number of executed events. + */ + std::size_t execute_expired_events(); + /** + * Remove (and thus cancel) all the timed events with the given name. + * Returns the number of canceled events. + */ + std::size_t cancel(const std::string& name); + /** + * Return the number of managed events. + */ + std::size_t size() const; + +private: + std::vector events; + explicit TimedEventsManager() = default; +}; diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp new file mode 100644 index 0000000..67d61fe --- /dev/null +++ b/src/utils/timed_events_manager.cpp @@ -0,0 +1,73 @@ +#include + +TimedEventsManager& TimedEventsManager::instance() +{ + static TimedEventsManager inst; + return inst; +} + +void TimedEventsManager::add_event(TimedEvent&& event) +{ + for (auto it = this->events.begin(); it != this->events.end(); ++it) + { + if (it->is_after(event)) + { + this->events.emplace(it, std::move(event)); + return; + } + } + this->events.emplace_back(std::move(event)); +} + +std::chrono::milliseconds TimedEventsManager::get_timeout() const +{ + if (this->events.empty()) + return utils::no_timeout; + return this->events.front().get_timeout(); +} + +std::size_t TimedEventsManager::execute_expired_events() +{ + std::size_t count = 0; + const auto now = std::chrono::steady_clock::now(); + for (auto it = this->events.begin(); it != this->events.end();) + { + if (!it->is_after(now)) + { + TimedEvent copy(std::move(*it)); + it = this->events.erase(it); + ++count; + copy.execute(); + if (copy.repeat) + { + copy.time_point += copy.repeat_delay; + this->add_event(std::move(copy)); + } + continue; + } + else + break; + } + return count; +} + +std::size_t TimedEventsManager::cancel(const std::string& name) +{ + std::size_t res = 0; + for (auto it = this->events.begin(); it != this->events.end();) + { + if (it->get_name() == name) + { + it = this->events.erase(it); + res++; + } + else + ++it; + } + return res; +} + +std::size_t TimedEventsManager::size() const +{ + return this->events.size(); +} diff --git a/src/utils/tolower.cpp b/src/utils/tolower.cpp new file mode 100644 index 0000000..3e518bd --- /dev/null +++ b/src/utils/tolower.cpp @@ -0,0 +1,13 @@ +#include + +namespace utils +{ + std::string tolower(const std::string& original) + { + std::string res; + res.reserve(original.size()); + for (const char c: original) + res += static_cast(std::tolower(c)); + return res; + } +} diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp new file mode 100644 index 0000000..650e05d --- /dev/null +++ b/src/utils/tolower.hpp @@ -0,0 +1,11 @@ +#pragma once + + +#include + +namespace utils +{ + std::string tolower(const std::string& original); +} + + diff --git a/src/utils/xdg.cpp b/src/utils/xdg.cpp new file mode 100644 index 0000000..b0fa7be --- /dev/null +++ b/src/utils/xdg.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include "biboumi.h" + +std::string xdg_path(const std::string& filename, const char* env_var) +{ + const char* xdg_home = ::getenv(env_var); + if (xdg_home && xdg_home[0] == '/') + return std::string{xdg_home} + "/" PROJECT_NAME "/" + filename; + else + { + const char* home = ::getenv("HOME"); + if (home) + return std::string{home} + "/" ".config" "/" PROJECT_NAME "/" + filename; + else + return filename; + } +} + +std::string xdg_config_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_CONFIG_HOME"); +} + +std::string xdg_data_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_DATA_HOME"); +} diff --git a/src/utils/xdg.hpp b/src/utils/xdg.hpp new file mode 100644 index 0000000..56e11da --- /dev/null +++ b/src/utils/xdg.hpp @@ -0,0 +1,14 @@ +#pragma once + + +#include + +/** + * Returns a path for the given filename, according to the XDG base + * directory specification, see + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ +std::string xdg_config_path(const std::string& filename); +std::string xdg_data_path(const std::string& filename); + + diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp new file mode 100644 index 0000000..825cc92 --- /dev/null +++ b/src/xmpp/adhoc_command.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +using namespace std::string_literals; + +AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): + name(name), + callbacks(std::move(callbacks)), + admin_only(admin_only) +{ +} + +bool AdhocCommand::is_admin_only() const +{ + return this->admin_only; +} + +void PingStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) +{ + XmlSubNode note(command_node, "note"); + note["type"] = "info"; + note.set_inner("Pong"); +} + +void HelloStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) +{ + XmlSubNode x(command_node, "jabber:x:data:x"); + x["type"] = "form"; + XmlSubNode title(x, "title"); + title.set_inner("Configure your name."); + XmlSubNode instructions(x, "instructions"); + instructions.set_inner("Please provide your name."); + XmlSubNode name_field(x, "field"); + name_field["var"] = "name"; + name_field["type"] = "text-single"; + name_field["label"] = "Your name"; + XmlSubNode required(name_field, "required"); +} + +void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + // Find out if the name was provided in the form. + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) + { + const XmlNode* name_field = nullptr; + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "name") + { + name_field = field; + break; + } + if (name_field) + { + if (const XmlNode* value = name_field->get_child("value", "jabber:x:data")) + { + const std::string value_str = value->get_inner(); + command_node.delete_all_children(); + XmlSubNode note(command_node, "note"); + note["type"] = "info"; + note.set_inner("Hello "s + value_str + "!"s); + return; + } + } + } + command_node.delete_all_children(); + XmlSubNode error(command_node, ADHOC_NS":error"); + error["type"] = "modify"; + XmlSubNode condition(error, STANZA_NS":bad-request"); + session.terminate(); +} + +void Reload(XmppComponent&, AdhocSession&, XmlNode& command_node) +{ + ::reload_process(); + command_node.delete_all_children(); + XmlSubNode note(command_node, "note"); + note["type"] = "info"; + note.set_inner("Configuration reloaded."); +} diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp new file mode 100644 index 0000000..7c4de47 --- /dev/null +++ b/src/xmpp/adhoc_command.hpp @@ -0,0 +1,44 @@ +#pragma once + +/** + * Describe an ad-hoc command. + * + * Can only have zero or one step for now. When execution is requested, it + * can return a result immediately, or provide a form to be filled, and + * provide a result once the filled form is received. + */ + +#include + +#include +#include + +class AdhocCommand +{ + friend class AdhocSession; +public: + AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); + ~AdhocCommand() = default; + AdhocCommand(const AdhocCommand&) = default; + AdhocCommand(AdhocCommand&&) = default; + AdhocCommand& operator=(AdhocCommand&&) = delete; + AdhocCommand& operator=(const AdhocCommand&) = delete; + + const std::string name; + + bool is_admin_only() const; + +private: + /** + * A command may have one or more steps. Each step is a different + * callback, inserting things into a XmlNode and calling + * methods of an AdhocSession. + */ + std::vector callbacks; + const bool admin_only; +}; + +void PingStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void HelloStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void Reload(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp new file mode 100644 index 0000000..040d0ff --- /dev/null +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -0,0 +1,111 @@ +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::string_literals; + +const std::map& AdhocCommandsHandler::get_commands() const +{ + return this->commands; +} + +void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) +{ + const auto found = this->commands.find(name); + if (found != this->commands.end()) + throw std::runtime_error("Trying to add an ad-hoc command that already exist: "s + name); + this->commands.emplace(std::make_pair(std::move(name), std::move(command))); +} + +XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, const std::string& to, 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()) + { + XmlSubNode error(command_node, ADHOC_NS":error"); + error["type"] = "cancel"; + XmlSubNode condition(error, STANZA_NS":item-not-found"); + } + else if (command_it->second.is_admin_only() && + Config::get("admin", "") != jid.local + "@" + jid.domain) + { + XmlSubNode error(command_node, ADHOC_NS":error"); + error["type"] = "cancel"; + XmlSubNode condition(error, STANZA_NS":forbidden"); + } + 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, to)); + 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()) && + (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"; + XmlSubNode actions(command_node, "actions"); + XmlSubNode next(actions, "next"); + } + } + else if (action == "cancel") + { + this->sessions.erase(session_it); + command_node["status"] = "canceled"; + TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + } + else // unsupported action + { + XmlSubNode error(command_node, ADHOC_NS":error"); + error["type"] = "modify"; + XmlSubNode condition(error, STANZA_NS":bad-request"); + } + } + 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 new file mode 100644 index 0000000..e37d913 --- /dev/null +++ b/src/xmpp/adhoc_commands_handler.hpp @@ -0,0 +1,71 @@ +#pragma once + +/** + * Manage a list of available AdhocCommands and the list of ongoing + * AdhocCommandSessions. + */ + +#include +#include + +#include +#include +#include + +class AdhocCommandsHandler +{ +public: + explicit AdhocCommandsHandler(XmppComponent& xmpp_component): + xmpp_component(xmpp_component), + commands{} + { } + ~AdhocCommandsHandler() = default; + + AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; + AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; + + /** + * Returns the list of available commands. + */ + const std::map& get_commands() const; + /** + * Add a command into the list, associated with the given name + */ + void add_command(std::string name, AdhocCommand command); + /** + * 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 node containing one or more useful children. If + * it contains an node, the iq response will have an error type. + * + * Takes a copy of the node so we can actually edit it and use + * it as our return value. + */ + XmlNode handle_request(const std::string& executor_jid, const std::string& to, 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: + /** + * To access basically anything in the gateway. + */ + XmppComponent& xmpp_component; + /** + * The list of all available commands. + */ + std::map commands; + /** + * The list of all currently on-going commands. + * + * Of the form: {{session_id, owner_jid}, session}. + */ + std::map, AdhocSession> sessions; +}; diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp new file mode 100644 index 0000000..dda4bea --- /dev/null +++ b/src/xmpp/adhoc_session.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include + +AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner_jid, + const std::string& to_jid): + command(command), + owner_jid(owner_jid), + to_jid(to_jid), + current_step(0), + terminated(false) +{ +} + +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 new file mode 100644 index 0000000..0de8d13 --- /dev/null +++ b/src/xmpp/adhoc_session.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include +#include + +class XmppComponent; + +class AdhocCommand; +class AdhocSession; + +/** + * A function executed as an ad-hoc command step. It takes a + * XmlNode and modifies it accordingly (inserting for example an + * node, or a data form…). + */ +using AdhocStep = std::function; + +class AdhocSession +{ +public: + explicit AdhocSession(const AdhocCommand& command, const std::string& owner_jid, + const std::string& to_jid); + ~AdhocSession() = default; + + AdhocSession(const AdhocSession&) = delete; + AdhocSession(AdhocSession&&) = delete; + AdhocSession& operator=(const AdhocSession&) = delete; + AdhocSession& operator=(AdhocSession&&) = delete; + + /** + * 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; + std::string get_target_jid() const + { + return this->to_jid; + } + std::string get_owner_jid() const + { + return this->owner_jid; + } + +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 'to' attribute in the request stanza. This is the target of the current session. + */ + const std::string& to_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; + +public: + /** + * A map to store various things that we may want to remember between two + * steps of the same session. A step can insert any value associated to + * any key in there. + */ + std::map vars; +}; diff --git a/src/xmpp/auth.cpp b/src/xmpp/auth.cpp new file mode 100644 index 0000000..8a34a4e --- /dev/null +++ b/src/xmpp/auth.cpp @@ -0,0 +1,8 @@ +#include + +#include + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret) +{ + return sha1(stream_id + secret); +} diff --git a/src/xmpp/auth.hpp b/src/xmpp/auth.hpp new file mode 100644 index 0000000..34a2116 --- /dev/null +++ b/src/xmpp/auth.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret); + diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4ba5e65..1c0f65c 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -16,7 +16,6 @@ #include -#include #include #include diff --git a/src/xmpp/body.hpp b/src/xmpp/body.hpp new file mode 100644 index 0000000..068d1a4 --- /dev/null +++ b/src/xmpp/body.hpp @@ -0,0 +1,12 @@ +#pragma once + + +namespace Xmpp +{ +// Contains: +// - an XMPP-valid UTF-8 body +// - an XML node representing the XHTML-IM body, or null + using body = std::tuple>; +} + + diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp new file mode 100644 index 0000000..0751387 --- /dev/null +++ b/src/xmpp/jid.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include + +#include +#ifdef LIBIDN_FOUND + #include + #include + #include + #include + #include + #include +#endif + +#include + +Jid::Jid(const std::string& jid) +{ + std::string::size_type slash = jid.find('/'); + if (slash != std::string::npos) + { + this->resource = jid.substr(slash + 1); + } + + std::string::size_type at = jid.find('@'); + if (at != std::string::npos && at < slash) + { + this->local = jid.substr(0, at); + at++; + } + else + at = 0; + + this->domain = jid.substr(at, slash - at); +} + +static constexpr size_t max_jid_part_len = 1023; + +std::string jidprep(const std::string& original) +{ +#ifdef LIBIDN_FOUND + using CacheType = std::map; + static CacheType cache; + std::pair cached = cache.insert({original, {}}); + if (std::get<1>(cached) == false) + { // Insertion failed: the result is already in the cache, return it + return std::get<0>(cached)->second; + } + + const std::string error_msg("Failed to convert " + original + " into a valid JID:"); + Jid jid(original); + + char local[max_jid_part_len] = {}; + memcpy(local, jid.local.data(), std::min(max_jid_part_len, jid.local.size())); + Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, + static_cast(0), stringprep_xmpp_nodeprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + + char domain[max_jid_part_len] = {}; + memcpy(domain, jid.domain.data(), std::min(max_jid_part_len, jid.domain.size())); + + { + // Using getaddrinfo, check if the domain part is a valid IPv4 (then use + // it as is), or IPv6 (surround it with []), or a domain name (run + // nameprep) + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + + struct addrinfo* addr_res = nullptr; + const auto ret = ::getaddrinfo(domain, nullptr, &hints, &addr_res); + auto addrinfo_deleter = utils::make_scope_guard([addr_res] { if (addr_res) freeaddrinfo(addr_res); }); + if (ret || !addr_res || (addr_res->ai_family != AF_INET && addr_res->ai_family != AF_INET6)) + { // Not an IP, run nameprep on it + rc = static_cast(::stringprep(domain, max_jid_part_len, + static_cast(0), stringprep_nameprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + + // Make sure it contains only allowed characters + using std::begin; + using std::end; + char* domain_end = domain + ::strlen(domain); + std::replace_if(std::begin(domain), domain + ::strlen(domain), + [](const char c) -> bool + { + return !((c >= 'a' && c <= 'z') || c == '-' || + (c >= '0' && c <= '9') || c == '.'); + }, '-'); + // Make sure there are no doubled - or . + std::set special_chars{'-', '.'}; + domain_end = std::unique(begin(domain), domain + ::strlen(domain), [&special_chars](const char& a, const char& b) -> bool + { + return special_chars.count(a) && special_chars.count(b); + }); + // remove leading and trailing -. if any + if (domain_end != domain && special_chars.count(*(domain_end - 1))) + --domain_end; + if (domain_end != domain && special_chars.count(domain[0])) + { + std::memmove(domain, domain + 1, domain_end - domain + 1); + --domain_end; + } + // And if the final result is an empty string, return a dummy hostname + if (domain_end == domain) + ::strcpy(domain, "empty"); + else + *domain_end = '\0'; + } + else if (addr_res->ai_family == AF_INET6) + { // IPv6, surround it with []. The length is always enough: + // the longest possible IPv6 is way shorter than max_jid_part_len + ::memmove(domain + 1, domain, jid.domain.size()); + domain[0] = '['; + domain[jid.domain.size() + 1] = ']'; + } + } + + + // If there is no resource, stop here + if (jid.resource.empty()) + { + std::get<0>(cached)->second = std::string(local) + "@" + domain; + return std::get<0>(cached)->second; + } + + // Otherwise, also process the resource part + char resource[max_jid_part_len] = {}; + memcpy(resource, jid.resource.data(), std::min(max_jid_part_len, jid.resource.size())); + rc = static_cast(::stringprep(resource, max_jid_part_len, + static_cast(0), stringprep_xmpp_resourceprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource; + return std::get<0>(cached)->second; + +#else + (void)original; + return ""; +#endif +} diff --git a/src/xmpp/jid.hpp b/src/xmpp/jid.hpp new file mode 100644 index 0000000..85e835c --- /dev/null +++ b/src/xmpp/jid.hpp @@ -0,0 +1,49 @@ +#pragma once + + +#include + +/** + * Parse a JID into its different subart + */ +class Jid +{ +public: + explicit Jid(const std::string& jid); + + Jid(const Jid&) = delete; + Jid(Jid&&) = delete; + Jid& operator=(const Jid&) = delete; + Jid& operator=(Jid&&) = delete; + + std::string domain; + std::string local; + std::string resource; + + std::string bare() const + { + return this->local + "@" + this->domain; + } + std::string full() const + { + std::string res = this->domain; + if (!this->local.empty()) + res = this->local + "@" + this->domain; + if (!this->resource.empty()) + res += "/" + this->resource; + return res; + } +}; + +/** + * Prepare the given UTF-8 string according to the XMPP node stringprep + * identifier profile. This is used to send properly-formed JID to the XMPP + * server. + * + * If the stringprep library is not found, we return an empty string. When + * this function is used, the result must always be checked for an empty + * value, and if this is the case it must not be used as a JID. + */ +std::string jidprep(const std::string& original); + + diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp new file mode 100644 index 0000000..1453b18 --- /dev/null +++ b/src/xmpp/xmpp_component.cpp @@ -0,0 +1,672 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#ifdef SYSTEMD_FOUND +# include +#endif + +using namespace std::string_literals; + +static std::set 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, const std::string& hostname, const std::string& secret): + TCPClientSocketHandler(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 = ""; + 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 + auto data = ""s + get_handshake_digest(this->stream_id, this->secret) + ""; + 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 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) +{ + Stanza node("stream:error"); + { + XmlSubNode error(node, name); + error["xmlns"] = STREAM_NS; + if (!explanation.empty()) + error.set_inner(explanation); + } + 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"; + { + XmlSubNode error(node, "error"); + error["type"] = error_type; + { + XmlSubNode inner_error(error, defined_condition); + inner_error["xmlns"] = STANZA_NS; + } + if (!text.empty()) + { + XmlSubNode text_node(error, "text"); + text_node["xmlns"] = STANZA_NS; + text_node.set_inner(text); + } + } + } + this->send_stanza(node); +} + +void XmppComponent::close_document() +{ + log_debug("XMPP SENDING: "); + this->send_data(""); + this->doc_open = false; +} + +void XmppComponent::handle_handshake(const 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::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, const bool nocopy) +{ + Stanza message("message"); + { + message["to"] = to; + if (fulljid) + message["from"] = from; + else + message["from"] = from + "@" + this->served_hostname; + if (!type.empty()) + message["type"] = type; + XmlSubNode body_node(message, "body"); + body_node.set_inner(std::get<0>(body)); + if (std::get<1>(body)) + { + XmlSubNode html(message, "html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::move(std::get<1>(body))); + } + if (nocopy) + { + XmlSubNode private_node(message, "private"); + private_node["xmlns"] = "urn:xmpp:carbons:2"; + XmlSubNode nocopy(message, "no-copy"); + nocopy["xmlns"] = "urn:xmpp:hints"; + } + } + this->send_stanza(message); +} + +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) +{ + Stanza presence("presence"); + { + presence["to"] = to; + presence["from"] = from + "@" + this->served_hostname + "/" + nick; + + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_USER_NS; + + XmlSubNode item(x, "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; + } + + if (self) + { + XmlSubNode status(x, "status"); + status["code"] = "110"; + } + } + this->send_stanza(presence); +} + +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"; + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_NS; + XmlSubNode error(presence, "error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = "cancel"; + XmlSubNode item_not_found(error, "item-not-found"); + item_not_found["xmlns"] = STANZA_NS; + XmlSubNode text(error, "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: #%@" + + this->served_hostname); + } + this->send_stanza(presence); +} + +void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) +{ + Stanza 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"; + XmlSubNode subject(message, "subject"); + subject.set_inner(std::get<0>(topic)); + } + 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"; + + { + XmlSubNode body(message, "body"); + body.set_inner(std::get<0>(xmpp_body)); + } + + if (std::get<1>(xmpp_body)) + { + XmlSubNode html(message, "html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::move(std::get<1>(xmpp_body))); + } + 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) +{ + Stanza message("message"); + message["to"] = jid_to; + if (!nick.empty()) + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else + message["from"] = muc_name + "@" + this->served_hostname; + message["type"] = "groupchat"; + + { + XmlSubNode body(message, "body"); + body.set_inner(body_txt); + } + { + XmlSubNode delay(message, "delay"); + delay["xmlns"] = DELAY_NS; + delay["from"] = muc_name + "@" + this->served_hostname; + delay["stamp"] = utils::to_string(timestamp); + } + + 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); + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_USER_NS; + if (self) + { + XmlSubNode status(x, "status"); + status["code"] = "110"; + } + if (!message_str.empty()) + { + XmlSubNode status(presence, "status"); + status.set_inner(message_str); + } + } + 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"; + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_USER_NS; + XmlSubNode item(x, "item"); + item["nick"] = new_nick; + XmlSubNode status(x, "status"); + status["code"] = "303"; + if (self) + { + XmlSubNode status(x, "status"); + status["code"] = "110"; + } + } + 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, const bool self) +{ + Stanza presence("presence"); + { + presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; + presence["to"] = jid_to; + presence["type"] = "unavailable"; + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_USER_NS; + XmlSubNode item(x, "item"); + item["affiliation"] = "none"; + item["role"] = "none"; + XmlSubNode actor(item, "actor"); + actor["nick"] = author; + actor["jid"] = author; // backward compatibility with old clients + XmlSubNode reason(item, "reason"); + reason.set_inner(txt); + XmlSubNode status(x, "status"); + status["code"] = "307"; + if (self) + { + XmlSubNode status(x, "status"); + status["code"] = "110"; + } + } + 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"; + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_NS; + XmlSubNode error(presence, "error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = type; + if (!text.empty()) + { + XmlSubNode text_node(error, "text"); + text_node["xmlns"] = STANZA_NS; + text_node.set_inner(text); + } + if (!error_code.empty()) + error["code"] = error_code; + XmlSubNode subnode(error, condition); + subnode["xmlns"] = STANZA_NS; + } + 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; + XmlSubNode x(presence, "x"); + x["xmlns"] = MUC_USER_NS; + XmlSubNode item(x, "item"); + item["affiliation"] = affiliation; + item["role"] = role; + } + 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; + { + XmlSubNode query(iq, "query"); + query["xmlns"] = VERSION_NS; + if (version.empty()) + { + { + XmlSubNode name(query, "name"); + name.set_inner("biboumi"); + } + { + XmlSubNode version(query, "version"); + version.set_inner(SOFTWARE_VERSION); + } + { + XmlSubNode os(query, "os"); + os.set_inner(utils::get_system_name()); + } + } + else + { + XmlSubNode name(query, "name"); + name.set_inner(version); + } + } + 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; + XmlSubNode query(iq, "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; + XmlSubNode item(query, "item"); + item["jid"] = from_jid; + item["node"] = kv.first; + item["name"] = kv.second.name; + } + } + 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; + XmlSubNode query(iq, "query"); + query["xmlns"] = VERSION_NS; + } + 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; +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp new file mode 100644 index 0000000..16d7480 --- /dev/null +++ b/src/xmpp/xmpp_component.hpp @@ -0,0 +1,245 @@ +#pragma once + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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" +#define DELAY_NS "urn:xmpp:delay" +#define MAM_NS "urn:xmpp:mam:1" +#define FORWARD_NS "urn:xmpp:forward:0" +#define CLIENT_NS "jabber:client" +#define DATAFORM_NS "jabber:x:data" +#define RSM_NS "http://jabber.org/protocol/rsm" +#define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic" + +/** + * 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 TCPClientSocketHandler +{ +public: + explicit XmppComponent(std::shared_ptr& poller, const std::string& hostname, const std::string& secret); + virtual ~XmppComponent() = default; + + XmppComponent(const XmppComponent&) = delete; + XmppComponent(XmppComponent&&) = delete; + XmppComponent& operator=(const XmppComponent&) = delete; + XmppComponent& operator=(XmppComponent&&) = delete; + + 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 a unique id, to be used in the 'id' element of our iq stanzas. + */ + static std::string next_id(); + bool is_document_open() const; + /** + * 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, const bool nocopy=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 the MUC topic to the user + */ + void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to, const std::string& who); + /** + * 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 a message, with a 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); + /** + * 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, const bool self); + /** + * 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 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, const std::string& from_jid, + const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler); + /** + * Send an iq version request + */ + void send_iq_version_request(const std::string& from, + const std::string& jid_to); + /** + * Send an empty iq of type result + */ + void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); + void send_iq_result_full_jid(const std::string& id, const std::string& to_jid, + const std::string& from_full_jid); + + void handle_handshake(const Stanza& stanza); + void handle_error(const Stanza& stanza); + + virtual void after_handshake() {} + + const std::string& get_served_hostname() const + { return this->served_hostname; } + + /** + * 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 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 secret; + bool authenticated; + /** + * Whether or not OUR XMPP document is open + */ + bool doc_open; +protected: + std::string served_hostname; + + std::unordered_map> stanza_handlers; + AdhocCommandsHandler adhoc_commands_handler; +}; + + diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp new file mode 100644 index 0000000..0488be9 --- /dev/null +++ b/src/xmpp/xmpp_parser.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include + +/** + * Expat handlers. Called by the Expat library, never by ourself. + * They just forward the call to the XmppParser corresponding methods. + */ + +static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts) +{ + static_cast(user_data)->start_element(name, atts); +} + +static void end_element_handler(void* user_data, const XML_Char* name) +{ + static_cast(user_data)->end_element(name); +} + +static void character_data_handler(void *user_data, const XML_Char *s, int len) +{ + static_cast(user_data)->char_data(s, len); +} + +/** + * XmppParser class + */ + +XmppParser::XmppParser(): + level(0), + current_node(nullptr), + root(nullptr) +{ + this->init_xml_parser(); +} + +void XmppParser::init_xml_parser() +{ + // Create the expat parser + this->parser = XML_ParserCreateNS("UTF-8", ':'); + XML_SetUserData(this->parser, static_cast(this)); + + // Install Expat handlers + XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler); + XML_SetCharacterDataHandler(this->parser, &character_data_handler); +} + +XmppParser::~XmppParser() +{ + XML_ParserFree(this->parser); +} + +int XmppParser::feed(const char* data, const int len, const bool is_final) +{ + int res = XML_Parse(this->parser, data, len, is_final); + if (res == XML_STATUS_ERROR && + (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) + log_error("Xml_Parse encountered an error: ", + XML_ErrorString(XML_GetErrorCode(this->parser))); + return res; +} + +int XmppParser::parse(const int len, const bool is_final) +{ + int res = XML_ParseBuffer(this->parser, len, is_final); + if (res == XML_STATUS_ERROR) + log_error("Xml_Parsebuffer encountered an error: ", + XML_ErrorString(XML_GetErrorCode(this->parser))); + return res; +} + +void XmppParser::reset() +{ + XML_ParserFree(this->parser); + this->init_xml_parser(); + this->current_node = nullptr; + this->root.reset(nullptr); + this->level = 0; +} + +void* XmppParser::get_buffer(const size_t size) const +{ + return XML_GetBuffer(this->parser, static_cast(size)); +} + +void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) +{ + this->level++; + + auto new_node = std::make_unique(name, this->current_node); + auto new_node_ptr = new_node.get(); + if (this->current_node) + this->current_node->add_child(std::move(new_node)); + else + this->root = std::move(new_node); + this->current_node = new_node_ptr; + for (size_t i = 0; attribute[i]; i += 2) + this->current_node->set_attribute(attribute[i], attribute[i+1]); + if (this->level == 1) + this->stream_open_event(*this->current_node); +} + +void XmppParser::end_element(const XML_Char*) +{ + this->level--; + if (this->level == 0) + { // End of the whole stream + this->stream_close_event(*this->current_node); + this->current_node = nullptr; + this->root.reset(); + } + else + { + auto parent = this->current_node->get_parent(); + if (this->level == 1) + { // End of a stanza + this->stanza_event(*this->current_node); + // Note: deleting all the children of our parent deletes ourself, + // so current_node is an invalid pointer after this line + parent->delete_all_children(); + } + this->current_node = parent; + } +} + +void XmppParser::char_data(const XML_Char* data, const size_t len) +{ + if (this->current_node->has_children()) + this->current_node->get_last_child()->add_to_tail({data, len}); + else + this->current_node->add_to_inner({data, len}); +} + +void XmppParser::stanza_event(const Stanza& stanza) const +{ + for (const auto& callback: this->stanza_callbacks) + { + try { + callback(stanza); + } catch (const std::exception& e) { + log_error("Unhandled exception: ", e.what()); + } + } +} + +void XmppParser::stream_open_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_open_callbacks) + callback(node); +} + +void XmppParser::stream_close_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_close_callbacks) + callback(node); +} + +void XmppParser::add_stanza_callback(std::function&& callback) +{ + this->stanza_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_open_callback(std::function&& callback) +{ + this->stream_open_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_close_callback(std::function&& callback) +{ + this->stream_close_callbacks.emplace_back(std::move(callback)); +} diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp new file mode 100644 index 0000000..9d67228 --- /dev/null +++ b/src/xmpp/xmpp_parser.hpp @@ -0,0 +1,133 @@ +#pragma once + + +#include + +#include + +#include + +/** + * A SAX XML parser that builds XML nodes and spawns events when a complete + * stanza is received (an element of level 2), or when the document is + * opened/closed (an element of level 1) + * + * After a stanza_event has been spawned, we delete the whole stanza. This + * means that even with a very long document (in XMPP the document is + * potentially infinite), the memory is never exhausted as long as each + * stanza is reasonnably short. + * + * The element names generated by expat contain the namespace of the + * element, a colon (':') and then the actual name of the element. To get + * an element "x" with a namespace of "http://jabber.org/protocol/muc", you + * just look for an XmlNode named "http://jabber.org/protocol/muc:x" + * + * TODO: enforce the size-limit for the stanza (limit the number of childs + * it can contain). For example forbid the parser going further than level + * 20 (arbitrary number here), and each XML node to have more than 15 childs + * (arbitrary number again). + */ +class XmppParser +{ +public: + explicit XmppParser(); + ~XmppParser(); + XmppParser(const XmppParser&) = delete; + XmppParser& operator=(const XmppParser&) = delete; + XmppParser(XmppParser&&) = delete; + XmppParser& operator=(XmppParser&&) = delete; + +public: + /** + * Feed the parser with some XML data + */ + int feed(const char* data, const int len, const bool is_final); + /** + * Parse the data placed in the parser buffer + */ + int parse(const int size, const bool is_final); + /** + * Reset the parser, so it can be used from scratch afterward + */ + void reset(); + /** + * Get a buffer provided by the xml parser. + */ + void* get_buffer(const size_t size) const; + /** + * Add one callback for the various events that this parser can spawn. + */ + void add_stanza_callback(std::function&& callback); + void add_stream_open_callback(std::function&& callback); + void add_stream_close_callback(std::function&& callback); + + /** + * Called when a new XML element has been opened. We instanciate a new + * XmlNode and set it as our current node. The parent of this new node is + * the previous "current" node. We have all the element's attributes in + * this event. + * + * We spawn a stream_event with this node if this is a level-1 element. + */ + void start_element(const XML_Char* name, const XML_Char** attribute); + /** + * Called when an XML element has been closed. We close the current_node, + * set our current_node as the parent of the current_node, and if that was + * a level-2 element we spawn a stanza_event with this node. + * + * And we then delete the stanza (and everything under it, its children, + * attribute, etc). + */ + void end_element(const XML_Char* name); + /** + * Some inner or tail data has been parsed + */ + void char_data(const XML_Char* data, const size_t len); + /** + * Calls all the stanza_callbacks one by one. + */ + void stanza_event(const Stanza& stanza) const; + /** + * Calls all the stream_open_callbacks one by one. Note: the passed node is not + * closed yet. + */ + void stream_open_event(const XmlNode& node) const; + /** + * Calls all the stream_close_callbacks one by one. + */ + void stream_close_event(const XmlNode& node) const; + +private: + /** + * Init the XML parser and install the callbacks + */ + void init_xml_parser(); + + /** + * Expat structure. + */ + XML_Parser parser; + /** + * The current depth in the XML document + */ + size_t level; + /** + * The deepest XML node opened but not yet closed (to which we are adding + * new children, inner or tail) + */ + XmlNode* current_node; + /** + * The root node has no parent, so we keep it here: the XmppParser object + * is its owner. + */ + std::unique_ptr root; + /** + * A list of callbacks to be called on an *_event, receiving the + * concerned Stanza/XmlNode. + */ + std::vector> stanza_callbacks; + std::vector> stream_open_callbacks; + std::vector> stream_close_callbacks; +}; + + diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp new file mode 100644 index 0000000..ac6ce9b --- /dev/null +++ b/src/xmpp/xmpp_stanza.cpp @@ -0,0 +1,229 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +std::string xml_escape(const std::string& data) +{ + std::string res; + res.reserve(data.size()); + for (size_t pos = 0; pos != data.size(); ++pos) + { + switch(data[pos]) + { + case '&': + res += "&"; + break; + case '<': + res += "<"; + break; + case '>': + res += ">"; + break; + case '\"': + res += """; + break; + case '\'': + res += "'"; + break; + default: + res += data[pos]; + break; + } + } + return res; +} + +std::string sanitize(const std::string& data, const std::string& encoding) +{ + if (utils::is_valid_utf8(data.data())) + return xml_escape(utils::remove_invalid_xml_chars(data)); + else + return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, encoding.data()))); +} + +XmlNode::XmlNode(const std::string& name, XmlNode* parent): + parent(parent) +{ + // split the namespace and the name + auto n = name.rfind(":"); + if (n == std::string::npos) + this->name = name; + else + { + this->name = name.substr(n+1); + this->attributes["xmlns"] = name.substr(0, n); + } +} + +XmlNode::XmlNode(const std::string& name): + XmlNode(name, nullptr) +{ +} + +void XmlNode::delete_all_children() +{ + this->children.clear(); +} + +void XmlNode::set_attribute(const std::string& name, const std::string& value) +{ + this->attributes[name] = value; +} + +void XmlNode::set_tail(const std::string& data) +{ + this->tail = data; +} + +void XmlNode::add_to_tail(const std::string& data) +{ + this->tail += data; +} + +void XmlNode::set_inner(const std::string& data) +{ + this->inner = data; +} + +void XmlNode::add_to_inner(const std::string& data) +{ + this->inner += data; +} + +std::string XmlNode::get_inner() const +{ + return this->inner; +} + +std::string XmlNode::get_tail() const +{ + return this->tail; +} + +const XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const +{ + for (const auto& child: this->children) + { + if (child->name == name && child->get_tag("xmlns") == xmlns) + return child.get(); + } + return nullptr; +} + +std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const +{ + std::vector res; + for (const auto& child: this->children) + { + if (child->name == name && child->get_tag("xmlns") == xmlns) + res.push_back(child.get()); + } + return res; +} + +XmlNode* XmlNode::add_child(std::unique_ptr child) +{ + child->parent = this; + auto ret = child.get(); + this->children.push_back(std::move(child)); + return ret; +} + +XmlNode* XmlNode::add_child(XmlNode&& child) +{ + auto new_node = std::make_unique(std::move(child)); + return this->add_child(std::move(new_node)); +} + +XmlNode* XmlNode::add_child(const XmlNode& child) +{ + auto new_node = std::make_unique(child); + return this->add_child(std::move(new_node)); +} + +XmlNode* XmlNode::get_last_child() const +{ + return this->children.back().get(); +} + +XmlNode* XmlNode::get_parent() const +{ + return this->parent; +} + +void XmlNode::set_name(const std::string& name) +{ + this->name = name; +} + +void XmlNode::set_name(std::string&& name) +{ + this->name = std::move(name); +} + +const std::string XmlNode::get_name() const +{ + return this->name; +} + +std::string XmlNode::to_string() const +{ + std::ostringstream res; + res << "<" << this->name; + for (const auto& it: this->attributes) + res << " " << it.first << "='" << sanitize(it.second) + "'"; + if (!this->has_children() && this->inner.empty()) + res << "/>"; + else + { + res << ">" + sanitize(this->inner); + for (const auto& child: this->children) + res << child->to_string(); + res << "get_name() << ">"; + } + res << sanitize(this->tail); + return res.str(); +} + +bool XmlNode::has_children() const +{ + return !this->children.empty(); +} + +const std::string& XmlNode::get_tag(const std::string& name) const +{ + try + { + const auto& value = this->attributes.at(name); + return value; + } + catch (const std::out_of_range& e) + { + static const std::string def{}; + return def; + } +} + +bool XmlNode::del_tag(const std::string& name) +{ + if (this->attributes.erase(name) != 0) + return true; + return false; +} + +std::string& XmlNode::operator[](const std::string& name) +{ + return this->attributes[name]; +} + +std::ostream& operator<<(std::ostream& os, const XmlNode& node) +{ + return os << node.to_string(); +} diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp new file mode 100644 index 0000000..f4b3948 --- /dev/null +++ b/src/xmpp/xmpp_stanza.hpp @@ -0,0 +1,160 @@ +#pragma once + + +#include +#include +#include +#include + +std::string xml_escape(const std::string& data); +std::string xml_unescape(const std::string& data); +std::string sanitize(const std::string& data, const std::string& encoding = "ISO-8859-1"); + +/** + * Represent an XML node. It has + * - A parent XML node (in the case of the first-level nodes, the parent is + nullptr) + * - zero, one or more children XML nodes + * - A name + * - A map of attributes + * - inner data (text inside the node) + * - tail data (text just after the node) + */ +class XmlNode +{ +public: + explicit XmlNode(const std::string& name, XmlNode* parent); + explicit XmlNode(const std::string& name); + /** + * The copy constructor does not copy the parent attribute. The children + * nodes are all copied recursively. + */ + XmlNode(const XmlNode& node): + name(node.name), + parent(nullptr), + attributes(node.attributes), + children{}, + inner(node.inner), + tail(node.tail) + { + for (const auto& child: node.children) + this->add_child(std::make_unique(*child)); + } + + XmlNode(XmlNode&& node) = default; + XmlNode& operator=(const XmlNode&) = delete; + XmlNode& operator=(XmlNode&&) = delete; + + ~XmlNode() = default; + + void delete_all_children(); + void set_attribute(const std::string& name, const std::string& value); + /** + * Set the content of the tail, that is the text just after this node + */ + void set_tail(const std::string& data); + /** + * Append the given data to the content of the tail. This exists because + * the expat library may provide the complete text of an element in more + * than one call + */ + void add_to_tail(const std::string& data); + /** + * Set the content of the inner, that is the text inside this node. + */ + void set_inner(const std::string& data); + /** + * Append the given data to the content of the inner. For the reason + * described in add_to_tail comment. + */ + void add_to_inner(const std::string& data); + /** + * Get the content of inner + */ + std::string get_inner() const; + /** + * Get the content of the tail + */ + std::string get_tail() const; + /** + * Get a pointer to the first child element with that name and that xml namespace + */ + const XmlNode* get_child(const std::string& name, const std::string& xmlns) const; + /** + * Get a vector of all the children that have that name and that xml namespace. + */ + std::vector get_children(const std::string& name, const std::string& xmlns) const; + /** + * Add a node child to this node. Assign this node to the child’s parent. + * Returns a pointer to the newly added child. + */ + XmlNode* add_child(std::unique_ptr child); + XmlNode* add_child(XmlNode&& child); + XmlNode* add_child(const XmlNode& child); + /** + * Returns the last of the children. If the node doesn't have any child, + * the behaviour is undefined. The user should make sure this is the case + * by calling has_children() for example. + */ + XmlNode* get_last_child() const; + XmlNode* get_parent() const; + void set_name(const std::string& name); + void set_name(std::string&& name); + const std::string get_name() const; + /** + * Serialize the stanza into a string + */ + std::string to_string() const; + /** + * Whether or not this node has at least one child (if not, this is a leaf + * node) + */ + bool has_children() const; + /** + * Gets the value for the given attribute, returns an empty string if the + * node as no such attribute. + */ + const std::string& get_tag(const std::string& name) const; + /** + * Remove the attribute of the node. Does nothing if that attribute is not + * present. Returns true if the tag was removed, false if it was absent. + */ + bool del_tag(const std::string& name); + /** + * Use this to set an attribute's value, like node["id"] = "12"; + */ + std::string& operator[](const std::string& name); + +private: + std::string name; + XmlNode* parent; + std::map attributes; + std::vector> children; + std::string inner; + std::string tail; +}; + +std::ostream& operator<<(std::ostream& os, const XmlNode& node); + +/** + * An XMPP stanza is just an XML node of level 2 in the XMPP document (the + * level 1 ones are the , and the ones above 2 are just the + * content of the stanzas) + */ +using Stanza = XmlNode; + +class XmlSubNode: public XmlNode +{ +public: + XmlSubNode(XmlNode& parent_ref, const std::string& name): + XmlNode(name), + parent_to_add(parent_ref) + {} + + ~XmlSubNode() + { + this->parent_to_add.add_child(std::move(*this)); + } +private: + XmlNode& parent_to_add; +}; \ No newline at end of file -- cgit v1.2.3 From 28a537a90a4262a83152b20bffdd6936f3320efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Mar 2017 21:47:09 +0100 Subject: Make things work with botan < 1.11.34 --- src/utils/sha1.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp index b77446e..2e6efc2 100644 --- a/src/utils/sha1.cpp +++ b/src/utils/sha1.cpp @@ -3,8 +3,10 @@ #include #ifdef BOTAN_FOUND +# include # include # include +# include #endif #ifdef GCRYPT_FOUND # include @@ -16,7 +18,13 @@ std::string sha1(const std::string& input) { #ifdef BOTAN_FOUND +# if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) + auto sha1 = Botan::HashFunction::create("SHA-1"); + if (!sha1) + throw Botan::Algorithm_Not_Found("SHA-1"); +# else auto sha1 = Botan::HashFunction::create_or_throw("SHA-1"); +# endif sha1->update(input); return Botan::hex_encode(sha1->final(), false); #endif -- cgit v1.2.3 From b7789fe586f375f09134a0817bd3ac19850c048f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Mar 2017 15:39:50 +0100 Subject: Add a Persistent option on channels fix #3230 --- src/bridge/bridge.cpp | 60 ++++++++++++++++++++++++++----------- src/bridge/bridge.hpp | 1 + src/irc/irc_client.cpp | 14 ++------- src/irc/irc_client.hpp | 2 +- src/xmpp/biboumi_adhoc_commands.cpp | 21 ++++++++++++- 5 files changed, 67 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7e2d8c1..d033acc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -62,7 +62,7 @@ void Bridge::shutdown(const std::string& exit_message) for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { it->second->send_quit_command(exit_message); - it->second->leave_dummy_channel(exit_message); + it->second->leave_dummy_channel(exit_message, {}); } } @@ -422,33 +422,48 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con if (!this->is_resource_in_chan(key, resource)) return ; + IrcChannel* channel = irc->get_channel(iid.get_local()); + auto nick = channel->get_self()->nick; + const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) { // Do not send a PART message if we actually are not in that channel // or if we already sent a PART but we are just waiting for the // acknowledgment from the server - IrcChannel* channel = irc->get_channel(iid.get_local()); - if (channel->joined && !channel->parting) - irc->send_part_command(iid.get_local(), status_message); + bool persistent = false; +#ifdef USE_DATABASE + const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, + iid.get_server(), iid.get_local()); + persistent = coptions.persistent.value(); +#endif + if (channel->joined && !channel->parting && !persistent) + { + const auto chan_name = iid.get_local(); + if (chan_name.empty()) + irc->leave_dummy_channel(status_message, resource); + else + irc->send_part_command(iid.get_local(), status_message); + } + else + { + this->send_muc_leave(std::move(iid), std::move(nick), "", true, resource); + } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); } else { - IrcChannel* chan = irc->get_channel(iid.get_local()); - if (chan) - { - auto nick = chan->get_self()->nick; - this->remove_resource_from_chan(key, resource); - this->send_muc_leave(std::move(iid), std::move(nick), - "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", - true, resource); - if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) - this->remove_resource_from_server(iid.get_server(), resource); - } + if (channel) + this->send_muc_leave(std::move(iid), std::move(nick), + "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", + true, resource); + this->remove_resource_from_chan(key, resource); + if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) + this->remove_resource_from_server(iid.get_server(), resource); } + } void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource) @@ -862,9 +877,13 @@ void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& me this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self); else - for (const auto& res: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), - this->user_jid + "/" + res, self); + { + for (const auto &res: this->resources_in_chan[iid.to_tuple()]) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + res, self); + this->remove_all_resources_from_chan(iid.to_tuple()); + + } IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) this->quit_or_start_linger_timer(iid.get_server()); @@ -1137,6 +1156,11 @@ bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::s return false; } +void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel_key) +{ + this->resources_in_chan.erase(channel_key); +} + void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) { auto it = this->resources_in_server.find(irc_hostname); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 73daae7..03eb716 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -312,6 +312,7 @@ private: void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource); void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource); bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const; + void remove_all_resources_from_chan(const ChannelKey& channel_key); std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const; void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 93e463b..00eab6f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -501,15 +501,7 @@ void IrcClient::send_private_message(const std::string& username, const std::str void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) { - IrcChannel* channel = this->get_channel(chan_name); - if (channel->joined == true) - { - if (chan_name.empty()) - this->leave_dummy_channel(status_message); - else - this->send_message(IrcMessage("PART", {chan_name, status_message})); - channel->parting = true; - } + this->send_message(IrcMessage("PART", {chan_name, status_message})); } void IrcClient::send_mode_command(const std::string& chan_name, const std::vector& arguments) @@ -1160,14 +1152,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel() return this->dummy_channel; } -void IrcClient::leave_dummy_channel(const std::string& exit_message) +void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource) { if (!this->dummy_channel.joined) return; this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true); + this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, resource); } #ifdef BOTAN_FOUND diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 009d0c9..435dce6 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -283,7 +283,7 @@ public: * Leave the dummy channel: forward a message to the user to indicate that * he left it, and mark it as not joined. */ - void leave_dummy_channel(const std::string& exit_message); + void leave_dummy_channel(const std::string& exit_message, const std::string& resource); const std::string& get_hostname() const { return this->hostname; } std::string get_nick() const { return this->current_nick; } diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index a83af80..5ec11da 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -256,7 +256,8 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode pass(x, "field"); pass["var"] = "pass"; pass["type"] = "text-private"; - pass["label"] = "Server password (to be used in a PASS command when connecting)"; + pass["label"] = "Server password"; + pass["desc"] = "Will be used in a PASS command when connecting"; if (!options.pass.value().empty()) { XmlSubNode pass_value(pass, "value"); @@ -463,6 +464,20 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); } + + XmlSubNode persistent(x, "field"); + persistent["var"] = "persistent"; + persistent["type"] = "boolean"; + persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command."; + persistent["label"] = "Persistent"; + { + XmlSubNode value(persistent, "value"); + value.set_name("value"); + if (options.persistent.value()) + value.set_inner("true"); + else + value.set_inner("false"); + } } void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -486,6 +501,10 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co else if (field->get_tag("var") == "encoding_in" && value && !value->get_inner().empty()) options.encodingIn = value->get_inner(); + + else if (field->get_tag("var") == "persistent" && + value) + options.persistent = to_bool(value->get_inner()); } options.update(); -- cgit v1.2.3 From 55f74349259fa0037de98d30d70b50396c4804f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 9 Mar 2017 17:37:49 +0100 Subject: Do not remove our resources if the QUIT message doesn't come from us --- src/bridge/bridge.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d033acc..4632c23 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -881,11 +881,12 @@ void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& me for (const auto &res: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + res, self); - this->remove_all_resources_from_chan(iid.to_tuple()); + if (self) + this->remove_all_resources_from_chan(iid.to_tuple()); } IrcClient* irc = this->find_irc_client(iid.get_server()); - if (irc && irc->number_of_joined_channels() == 0) + if (self && irc && irc->number_of_joined_channels() == 0) this->quit_or_start_linger_timer(iid.get_server()); } -- cgit v1.2.3 From a3844c1d9ff0e714d07f1a411f3babc31de96e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Mar 2017 23:09:58 +0200 Subject: Change mam namespace to mam:2, instead of mam:1 --- src/xmpp/xmpp_component.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 16d7480..d808fd5 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -27,7 +27,7 @@ #define ADHOC_NS "http://jabber.org/protocol/commands" #define PING_NS "urn:xmpp:ping" #define DELAY_NS "urn:xmpp:delay" -#define MAM_NS "urn:xmpp:mam:1" +#define MAM_NS "urn:xmpp:mam:2" #define FORWARD_NS "urn:xmpp:forward:0" #define CLIENT_NS "jabber:client" #define DATAFORM_NS "jabber:x:data" -- cgit v1.2.3 From e8ccfe97be6f7042b0960f40ce29a89801b9982e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Mar 2017 23:10:38 +0200 Subject: =?UTF-8?q?Add=20a=20missing=20=E2=80=9Cpass=20by=20reference?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/biboumi_component.cpp | 2 +- src/xmpp/biboumi_component.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 1c0f65c..41eb4b8 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -737,7 +737,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s this->send_stanza(iq); } -void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from) +void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from) { Stanza iq("iq"); { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 1d25e0e..25ec2dd 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -69,7 +69,7 @@ public: * Sends the allowed namespaces in MUC message, according to * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic */ - void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from); + void send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); /** * Send a ping request */ -- cgit v1.2.3 From ce2daa5ea093437bf8f14ba92a467d0ae688e6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Mar 2017 23:24:47 +0200 Subject: Respond to disco#info on MUC JIDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, some client won’t do MAM, since they don’t know biboumi supports it. --- src/xmpp/biboumi_component.cpp | 34 +++++++++++++++++++++++++++++++++- src/xmpp/biboumi_component.hpp | 1 + 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 41eb4b8..ab8e5e0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -422,7 +422,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if (iid.type == Iid::Type::Channel) { - if (node == MUC_TRAFFIC_NS) + log_debug("type_channel"); + if (node.empty()) + { + this->send_irc_channel_disco_info(id, from, to_str); + stanza_error.disable(); + } + else if (node == MUC_TRAFFIC_NS) { this->send_irc_channel_muc_traffic_info(id, from, to_str); stanza_error.disable(); @@ -755,6 +761,32 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id, this->send_stanza(iq); } +void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from) +{ + log_debug("jid_from: ", jid_from); + Jid from(jid_from); + Iid iid(from.local, {}); + Stanza iq("iq"); + { + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlSubNode query(iq, "query"); + query["xmlns"] = DISCO_INFO_NS; + XmlSubNode identity(query, "identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "IRC channel "s + 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}) + { + XmlSubNode feature(query, "feature"); + feature["var"] = ns; + } + } + this->send_stanza(iq); +} + void BiboumiComponent::send_ping_request(const std::string& from, const std::string& jid_to, const std::string& id) diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 25ec2dd..f416dac 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -70,6 +70,7 @@ public: * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic */ void send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); + void send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); /** * Send a ping request */ -- cgit v1.2.3 From 52b795d11976802cfec12d886fca508047ffed89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Mar 2017 23:26:09 +0200 Subject: Add a missing namespace in the server disco#info response --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ab8e5e0..6dc8ce9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -734,7 +734,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s identity["category"] = "conference"; identity["type"] = "irc"; identity["name"] = "IRC server "s + from.local + " over Biboumi"; - for (const char *ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS}) + for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) { XmlSubNode feature(query, "feature"); feature["var"] = ns; -- cgit v1.2.3 From 1a09c965eb3723cdaab9ea556f30ffbc7f09a6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Mar 2017 23:32:43 +0200 Subject: Remove two sneaky log_debug --- src/bridge/bridge.cpp | 33 +++++++++++++++++---------------- src/bridge/bridge.hpp | 14 +++++++------- src/bridge/colors.hpp | 2 +- src/config/config.cpp | 2 +- src/config/config.hpp | 2 +- src/database/database.cpp | 2 +- src/database/database.hpp | 2 +- src/irc/irc_client.cpp | 6 +++--- src/irc/irc_client.hpp | 2 +- src/irc/irc_message.cpp | 6 +++--- src/irc/irc_user.cpp | 2 +- src/irc/irc_user.hpp | 2 +- src/network/resolver.cpp | 13 ++++++------- src/utils/encoding.hpp | 2 +- src/utils/time.cpp | 2 +- src/xmpp/adhoc_command.hpp | 2 +- src/xmpp/adhoc_session.cpp | 2 +- src/xmpp/biboumi_adhoc_commands.cpp | 14 +++++++------- src/xmpp/biboumi_component.cpp | 2 -- src/xmpp/jid.cpp | 3 +-- src/xmpp/xmpp_component.cpp | 8 ++++---- src/xmpp/xmpp_component.hpp | 6 +++--- 22 files changed, 63 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4632c23..0d6ade3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -29,8 +30,8 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) #endif } -Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller): - user_jid(user_jid), +Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller): + user_jid(std::move(user_jid)), xmpp(xmpp), poller(poller) { @@ -59,10 +60,10 @@ static std::tuple get_role_affiliation_from_irc_mode(c void Bridge::shutdown(const std::string& exit_message) { - for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) + for (auto& pair: this->irc_clients) { - it->second->send_quit_command(exit_message); - it->second->leave_dummy_channel(exit_message, {}); + pair.second->send_quit_command(exit_message); + pair.second->leave_dummy_channel(exit_message, {}); } } @@ -168,7 +169,7 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource) { - const auto hostname = iid.get_server(); + const auto& hostname = iid.get_server(); this->cancel_linger_timer(hostname); IrcClient* irc = this->make_irc_client(hostname, nickname); this->add_resource_to_server(hostname, resource); @@ -439,7 +440,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con #endif if (channel->joined && !channel->parting && !persistent) { - const auto chan_name = iid.get_local(); + const auto& chan_name = iid.get_local(); if (chan_name.empty()) irc->leave_dummy_channel(status_message, resource); else @@ -447,7 +448,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con } else { - this->send_muc_leave(std::move(iid), std::move(nick), "", true, resource); + this->send_muc_leave(iid, std::move(nick), "", true, resource); } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID @@ -456,7 +457,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con else { if (channel) - this->send_muc_leave(std::move(iid), std::move(nick), + this->send_muc_leave(iid, std::move(nick), "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", true, resource); this->remove_resource_from_chan(key, resource); @@ -870,16 +871,16 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } -void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, +void Bridge::send_muc_leave(const Iid &iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource) { if (!resource.empty()) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message), this->user_jid + "/" + resource, self); else { for (const auto &res: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message), this->user_jid + "/" + res, self); if (self) this->remove_all_resources_from_chan(iid.to_tuple()); @@ -1157,9 +1158,9 @@ bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::s return false; } -void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel_key) +void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel) { - this->resources_in_chan.erase(channel_key); + this->resources_in_chan.erase(channel); } void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) @@ -1191,9 +1192,9 @@ bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, cons return false; } -std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel_key) const +std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel) const { - auto it = this->resources_in_chan.find(channel_key); + auto it = this->resources_in_chan.find(channel); if (it == this->resources_in_chan.end()) return 0; return it->second.size(); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 03eb716..53d2136 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -38,7 +38,7 @@ using irc_responder_callback_t = std::function& poller); + explicit Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller); ~Bridge() = default; Bridge(const Bridge&) = delete; @@ -169,7 +169,7 @@ public: /** * Send an unavailable presence from this participant */ - void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource=""); + void send_muc_leave(const Iid& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource = ""); /** * Send presences to indicate that an user old_nick (ourself if self == * true) changed his nick to new_nick. The user_mode is needed because @@ -309,11 +309,11 @@ private: /** * Manage which resource is in which channel */ - void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource); - void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource); - bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const; - void remove_all_resources_from_chan(const ChannelKey& channel_key); - std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const; + void add_resource_to_chan(const ChannelKey& channel, const std::string& resource); + void remove_resource_from_chan(const ChannelKey& channel, const std::string& resource); + bool is_resource_in_chan(const ChannelKey& channel, const std::string& resource) const; + void remove_all_resources_from_chan(const ChannelKey& channel); + std::size_t number_of_resources_in_chan(const ChannelKey& channel) const; void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource); diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index e2c8a87..dceed74 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -51,6 +51,6 @@ static const char irc_format_char[] = { * Returns the body cleaned from any IRC formatting (but without any xhtml), * and the body as XHTML-IM */ -Xmpp::body irc_format_to_xhtmlim(const std::string& str); +Xmpp::body irc_format_to_xhtmlim(const std::string& s); diff --git a/src/config/config.cpp b/src/config/config.cpp index 24a1c87..0db5751 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -37,7 +37,7 @@ void Config::set(const std::string& option, const std::string& value, bool save) } } -void Config::connect(t_config_changed_callback callback) +void Config::connect(const t_config_changed_callback& callback) { Config::callbacks.push_back(callback); } diff --git a/src/config/config.hpp b/src/config/config.hpp index 4e01281..2ba38cc 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -54,7 +54,7 @@ public: * configuration change occurs (when set() is called, or when the initial * conf is read) */ - static void connect(t_config_changed_callback); + static void connect(const t_config_changed_callback&); /** * Destroy the instance, forcing it to be recreated (with potentially * different parameters) the next time it’s needed. diff --git a/src/database/database.cpp b/src/database/database.cpp index cb0c78f..71b0c37 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -20,7 +20,7 @@ void Database::open(const std::string& filename, const std::string& db_type) "database="s + filename); if (new_db->needsUpgrade()) new_db->upgrade(); - Database::db.reset(new_db.release()); + Database::db = std::move(new_db); } catch (const litesql::DatabaseError& e) { log_error("Failed to open database ", filename, ". ", e.what()); throw; diff --git a/src/database/database.hpp b/src/database/database.hpp index 6823574..1665a9a 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -49,7 +49,7 @@ public: const std::string& server, const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, - int limit=-1, const std::string& before="", const std::string& after=""); + int limit=-1, const std::string& start="", const std::string& end=""); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 00eab6f..48b105d 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -888,7 +888,7 @@ void IrcClient::on_part(const IrcMessage& message) // channel pointer is now invalid channel = nullptr; } - this->bridge.send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); + this->bridge.send_muc_leave(iid, std::move(nick), std::move(txt), self); } } @@ -906,7 +906,7 @@ void IrcClient::on_error(const IrcMessage& message) if (!channel->joined) continue; std::string own_nick = channel->get_self()->nick; - this->bridge.send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); + this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true); } this->channels.clear(); this->send_gateway_message("ERROR: "s + leave_message); @@ -930,7 +930,7 @@ void IrcClient::on_quit(const IrcMessage& message) iid.set_local(chan_name); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false); + this->bridge.send_muc_leave(iid, std::move(nick), txt, false); } } } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 435dce6..8119201 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -52,7 +52,7 @@ public: /** * Close the connection, remove us from the poller */ - void on_connection_close(const std::string& error) override final; + void on_connection_close(const std::string& error_msg) override final; /** * Parse the data we have received so far and try to get one or more * complete messages from it. diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp index 966a47c..14fdb0e 100644 --- a/src/irc/irc_message.cpp +++ b/src/irc/irc_message.cpp @@ -8,12 +8,12 @@ IrcMessage::IrcMessage(std::string&& line) // optional prefix if (line[0] == ':') { - pos = line.find(" "); + pos = line.find(' '); this->prefix = line.substr(1, pos - 1); line = line.substr(pos + 1, std::string::npos); } // command - pos = line.find(" "); + pos = line.find(' '); this->command = line.substr(0, pos); line = line.substr(pos + 1, std::string::npos); // arguments @@ -24,7 +24,7 @@ IrcMessage::IrcMessage(std::string&& line) this->arguments.emplace_back(line.substr(1, std::string::npos)); break ; } - pos = line.find(" "); + pos = line.find(' '); this->arguments.emplace_back(line.substr(0, pos)); line = line.substr(pos + 1, std::string::npos); } while (pos != std::string::npos); diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index 9fa3612..139015e 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -21,7 +21,7 @@ IrcUser::IrcUser(const std::string& name, name_begin++; } - const std::string::size_type sep = name.find("!", name_begin); + const std::string::size_type sep = name.find('!', name_begin); if (sep == std::string::npos) this->nick = name.substr(name_begin); else diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp index c84030e..a4291d4 100644 --- a/src/irc/irc_user.hpp +++ b/src/irc/irc_user.hpp @@ -23,7 +23,7 @@ public: void add_mode(const char mode); void remove_mode(const char mode); - char get_most_significant_mode(const std::vector& sorted_user_modes) const; + char get_most_significant_mode(const std::vector& modes) const; std::string nick; std::string host; diff --git a/src/network/resolver.cpp b/src/network/resolver.cpp index db7fb32..ef54ba2 100644 --- a/src/network/resolver.cpp +++ b/src/network/resolver.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #ifdef UDNS_FOUND @@ -41,8 +41,8 @@ Resolver::Resolver(): void Resolver::resolve(const std::string& hostname, const std::string& port, SuccessCallbackType success_cb, ErrorCallbackType error_cb) { - this->error_cb = error_cb; - this->success_cb = success_cb; + this->error_cb = std::move(error_cb); + this->success_cb = std::move(success_cb); #ifdef UDNS_FOUND this->port = port; #endif @@ -52,8 +52,7 @@ void Resolver::resolve(const std::string& hostname, const std::string& port, int Resolver::call_getaddrinfo(const char *name, const char* port, int flags) { - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); + struct addrinfo hints{}; hints.ai_flags = flags; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; @@ -111,7 +110,7 @@ void Resolver::start_resolving(const std::string& hostname, const std::string& p // And finally, we try a DNS resolution auto hostname6_resolved = [](dns_ctx*, dns_rr_a6* result, void* data) { - Resolver* resolver = static_cast(data); + auto resolver = static_cast(data); resolver->on_hostname6_resolved(result); resolver->after_resolved(); std::free(result); @@ -119,7 +118,7 @@ void Resolver::start_resolving(const std::string& hostname, const std::string& p auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data) { - Resolver* resolver = static_cast(data); + auto resolver = static_cast(data); resolver->on_hostname4_resolved(result); resolver->after_resolved(); std::free(result); diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp index 586edd8..b707a0c 100644 --- a/src/utils/encoding.hpp +++ b/src/utils/encoding.hpp @@ -28,7 +28,7 @@ namespace utils * Convert the given string (encoded is "encoding") into valid utf-8. * If some decoding fails, insert an utf-8 placeholder character instead. */ - std::string convert_to_utf8(const std::string& str, const char* encoding); + std::string convert_to_utf8(const std::string& str, const char* charset); } namespace xep0106 diff --git a/src/utils/time.cpp b/src/utils/time.cpp index 8fa3fcd..88b3de3 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp index 7c4de47..ced4549 100644 --- a/src/xmpp/adhoc_command.hpp +++ b/src/xmpp/adhoc_command.hpp @@ -17,7 +17,7 @@ class AdhocCommand { friend class AdhocSession; public: - AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); + AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only); ~AdhocCommand() = default; AdhocCommand(const AdhocCommand&) = default; AdhocCommand(AdhocCommand&&) = default; diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp index dda4bea..e2d6c0e 100644 --- a/src/xmpp/adhoc_session.cpp +++ b/src/xmpp/adhoc_session.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner_jid, const std::string& to_jid): diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 5ec11da..85f945d 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -24,7 +24,7 @@ using namespace std::string_literals; void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) { - auto& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; @@ -55,7 +55,7 @@ void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { - auto& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); // Find out if the jids, and the quit message are provided in the form. std::string quit_message; @@ -151,7 +151,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { - BiboumiComponent& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) @@ -533,7 +533,7 @@ void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& } else { // Send a form to select the user to disconnect - auto& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; @@ -578,7 +578,7 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession& // Send a data form to let the user choose which server to disconnect the // user from command_node.delete_all_children(); - auto& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; @@ -643,7 +643,7 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& } } - auto& biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = dynamic_cast(xmpp_component); Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect); auto& clients = bridge->get_irc_clients(); @@ -671,7 +671,7 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node) { - BiboumiComponent& biboumi_component = static_cast(component); + auto& biboumi_component = dynamic_cast(component); const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6dc8ce9..df96d62 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -422,7 +422,6 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if (iid.type == Iid::Type::Channel) { - log_debug("type_channel"); if (node.empty()) { this->send_irc_channel_disco_info(id, from, to_str); @@ -763,7 +762,6 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id, void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from) { - log_debug("jid_from: ", jid_from); Jid from(jid_from); Iid iid(from.local, {}); Stanza iq("iq"); diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index 0751387..ba8d70b 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -68,8 +68,7 @@ std::string jidprep(const std::string& original) // Using getaddrinfo, check if the domain part is a valid IPv4 (then use // it as is), or IPv6 (surround it with []), or a domain name (run // nameprep) - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); + struct addrinfo hints{}; hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1453b18..6829cef 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -39,14 +39,14 @@ static std::set kickable_errors{ "malformed-error" }; -XmppComponent::XmppComponent(std::shared_ptr& poller, const std::string& hostname, const std::string& secret): +XmppComponent::XmppComponent(std::shared_ptr& poller, std::string hostname, std::string secret): TCPClientSocketHandler(poller), ever_auth(false), first_connection_try(true), - secret(secret), + secret(std::move(secret)), authenticated(false), doc_open(false), - served_hostname(hostname), + served_hostname(std::move(hostname)), stanza_handlers{}, adhoc_commands_handler(*this) { @@ -429,7 +429,7 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: 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) +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) { Stanza presence("presence"); { diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index d808fd5..250f0a8 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -43,7 +43,7 @@ class XmppComponent: public TCPClientSocketHandler { public: - explicit XmppComponent(std::shared_ptr& poller, const std::string& hostname, const std::string& secret); + explicit XmppComponent(std::shared_ptr& poller, std::string hostname, std::string secret); virtual ~XmppComponent() = default; XmppComponent(const XmppComponent&) = delete; @@ -91,7 +91,7 @@ public: * stanza, and explanation being a short human-readable sentence * describing the error. */ - void send_stream_error(const std::string& message, const std::string& explanation); + void send_stream_error(const std::string& name, const std::string& explanation); /** * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error */ @@ -143,7 +143,7 @@ public: /** * 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); + void send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); /** * Indicate that a participant changed his nick */ -- cgit v1.2.3 From 7f2127a7ea4c49fc1fbcd6cd6fb13e0265f4d841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 30 Mar 2017 18:16:44 +0200 Subject: Add the archive ID to messages when they are sent to users This makes us compatible with mam 6.0 fix #3249 --- src/bridge/bridge.cpp | 11 ++++++----- src/database/database.cpp | 14 +++++++++----- src/database/database.hpp | 2 +- src/xmpp/xmpp_component.cpp | 11 ++++++++++- src/xmpp/xmpp_component.hpp | 4 +++- 5 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 0d6ade3..2da1d96 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -254,15 +254,16 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) else irc->send_channel_message(iid.get_local(), line); + std::string uuid; #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); if (this->record_history) - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + uuid = Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), irc->get_own_nick()); #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), - this->make_xmpp_body(line), this->user_jid + "/" + resource); + this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), + this->user_jid + "/" + resource, uuid); } } @@ -839,8 +840,8 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { - this->xmpp.send_muc_message(std::to_string(iid), nick, - this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, {}); } } diff --git a/src/database/database.cpp b/src/database/database.cpp index 71b0c37..9f310da 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -119,14 +119,16 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_d return coptions; } -void Database::store_muc_message(const std::string& owner, const Iid& iid, - Database::time_point date, - const std::string& body, - const std::string& nick) +std::string Database::store_muc_message(const std::string& owner, const Iid& iid, + Database::time_point date, + const std::string& body, + const std::string& nick) { db::MucLogLine line(*Database::db); - line.uuid = Database::gen_uuid(); + auto uuid = Database::gen_uuid(); + + line.uuid = uuid; line.owner = owner; line.ircChanName = iid.get_local(); line.ircServerName = iid.get_server(); @@ -135,6 +137,8 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.nick = nick; line.update(); + + return uuid; } std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, diff --git a/src/database/database.hpp b/src/database/database.hpp index 1665a9a..b08a175 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -50,7 +50,7 @@ public: const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit=-1, const std::string& start="", const std::string& end=""); - static void store_muc_message(const std::string& owner, const Iid& iid, + static std::string store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); static void close(); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 6829cef..8335c8a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -380,7 +380,7 @@ 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) +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) { Stanza message("message"); message["to"] = jid_to; @@ -402,6 +402,15 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str // Pass the ownership of the pointer to this xmlnode html.add_child(std::move(std::get<1>(xmpp_body))); } + + if (!uuid.empty()) + { + XmlSubNode stanza_id(message, "stanza-id"); + stanza_id["xmlns"] = STABLE_ID_NS; + stanza_id["by"] = muc_name + "@" + this->served_hostname; + stanza_id["id"] = std::move(uuid); + } + this->send_stanza(message); } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 250f0a8..8eabaf6 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -33,6 +33,7 @@ #define DATAFORM_NS "jabber:x:data" #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" /** * An XMPP component, communicating with an XMPP server using the protocole @@ -134,7 +135,8 @@ 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); + void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to, + std::string uuid); /** * Send a message, with a element, part of a MUC history */ -- cgit v1.2.3 From e4cc69607c91db43cf154326aaba8afbe97a4c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Apr 2017 18:40:30 +0200 Subject: Handle some iq of type='error' as valid ping response fix #3251 --- src/xmpp/biboumi_component.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index df96d62..dc57eeb 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -564,6 +564,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) else if (type == "error") { stanza_error.disable(); + const auto it = this->waiting_iq.find(id); + if (it != this->waiting_iq.end()) + { + it->second(bridge, stanza); + this->waiting_iq.erase(it); + } } } catch (const IRCNotConnected& ex) @@ -807,8 +813,14 @@ void BiboumiComponent::send_ping_request(const std::string& from, { log_error("Received a corresponding ping result, but the 'to' from " "the response mismatches the 'from' of the request"); + return; } - else + const std::string type = stanza.get_tag("type"); + const XmlNode* error = stanza.get_child("error", COMPONENT_NS); + // Check if what we receive is considered a valid response. And yes, those errors are valid responses + if (type == "result" || + (type == "error" && error && (error->get_child("feature-not-implemented", STANZA_NS) || + error->get_child("service-unavailable", STANZA_NS)))) bridge->send_irc_ping_result({from, bridge}, id); }; this->waiting_iq[id] = result_cb; -- cgit v1.2.3 From 0878a0342c5a2ae6abcfaecc2d9f0c9d3fd0dbad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Apr 2017 21:38:53 +0200 Subject: =?UTF-8?q?Do=20not=20allow=20pings=20from=20resources=20that=20ar?= =?UTF-8?q?en=E2=80=99t=20in=20the=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #3252 --- src/bridge/bridge.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 2da1d96..4966b0d 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -744,9 +744,10 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) { + Jid from(to_jid); IrcClient* irc = this->get_irc_client(iid.get_server()); IrcChannel* chan = irc->get_channel(iid.get_local()); - if (!chan->joined) + if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource)) { this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed", "", true); -- cgit v1.2.3 From 5402a256d1f0ebbeafa32d250d000cf38fe748fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Apr 2017 18:45:24 +0200 Subject: Apply all the clang-tidy modernize-* fixes --- src/bridge/colors.cpp | 2 +- src/identd/identd_socket.hpp | 1 - src/irc/iid.cpp | 9 ++++--- src/irc/iid.hpp | 2 +- src/irc/irc_client.cpp | 41 ++++++++++++++++--------------- src/irc/irc_client.hpp | 6 ++--- src/main.cpp | 2 +- src/network/poller.cpp | 4 +-- src/network/tcp_client_socket_handler.cpp | 2 +- src/network/tcp_server_socket.hpp | 1 - src/network/tcp_socket_handler.cpp | 2 +- src/utils/encoding.cpp | 2 +- src/utils/timed_events.cpp | 13 +++++----- src/utils/timed_events.hpp | 4 +-- src/xmpp/adhoc_command.cpp | 5 ++-- src/xmpp/adhoc_command.hpp | 2 +- src/xmpp/biboumi_component.cpp | 10 +++----- src/xmpp/jid.cpp | 2 +- src/xmpp/xmpp_stanza.cpp | 2 +- 19 files changed, 56 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 66f51ee..7662425 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include using namespace std::string_literals; diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp index 10cb797..a386d80 100644 --- a/src/identd/identd_socket.hpp +++ b/src/identd/identd_socket.hpp @@ -2,7 +2,6 @@ #include -#include #include #include diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 6b07793..63c7039 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,10 +8,10 @@ constexpr char Iid::separator[]; -Iid::Iid(const std::string& local, const std::string& server, Iid::Type type): - type(type), - local(local), - server(server) +Iid::Iid(std::string local, std::string server, Iid::Type type): + type(std::move(type)), + local(std::move(local)), + server(std::move(server)) { } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 81cf3ca..89f4797 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -59,7 +59,7 @@ public: Iid(const std::string& iid, const std::set& chantypes); Iid(const std::string& iid, const std::initializer_list& chantypes); Iid(const std::string& iid, const Bridge* bridge); - Iid(const std::string& local, const std::string& server, Type type); + Iid(std::string local, std::string server, Type type); Iid() = default; Iid(const Iid&) = default; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 48b105d..710ba15 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -127,16 +128,16 @@ static const std::unordered_map& poller, const std::string& hostname, - const std::string& nickname, const std::string& username, - const std::string& realname, const std::string& user_hostname, +IrcClient::IrcClient(std::shared_ptr& poller, std::string hostname, + std::string nickname, std::string username, + std::string realname, std::string user_hostname, Bridge& bridge): TCPClientSocketHandler(poller), - hostname(hostname), - user_hostname(user_hostname), - username(username), - realname(realname), - current_nick(nickname), + hostname(std::move(hostname)), + user_hostname(std::move(user_hostname)), + username(std::move(username)), + realname(std::move(realname)), + current_nick(std::move(nickname)), bridge(bridge), welcomed(false), chanmodes({"", "", "", ""}), @@ -791,10 +792,10 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) { const std::string nickname = message.arguments[1]; this->on_generic_error(message); - for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + for (const auto& pair: this->channels) { Iid iid; - iid.set_local(it->first); + iid.set_local(pair.first); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; this->bridge.send_nickname_conflict_error(iid, nickname); @@ -808,10 +809,10 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) if (message.arguments.size() >= 3) txt = message.arguments[2]; this->on_generic_error(message); - for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + for (const auto& pair: this->channels) { Iid iid; - iid.set_local(it->first); + iid.set_local(pair.first); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; this->bridge.send_presence_error(iid, nickname, @@ -896,13 +897,13 @@ void IrcClient::on_error(const IrcMessage& message) { const std::string leave_message = message.arguments[0]; // The user is out of all the channels - for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + for (const auto& pair: this->channels) { Iid iid; - iid.set_local(it->first); + iid.set_local(pair.first); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - IrcChannel* channel = it->second.get(); + IrcChannel* channel = pair.second.get(); if (!channel->joined) continue; std::string own_nick = channel->get_self()->nick; @@ -917,10 +918,10 @@ void IrcClient::on_quit(const IrcMessage& message) std::string txt; if (message.arguments.size() >= 1) txt = message.arguments[0]; - for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + for (const auto& pair: this->channels) { - const std::string chan_name = it->first; - IrcChannel* channel = it->second.get(); + const std::string& chan_name = pair.first; + IrcChannel* channel = pair.second.get(); const IrcUser* user = channel->find_user(message.prefix); if (user) { @@ -966,9 +967,9 @@ void IrcClient::on_nick(const IrcMessage& message) { change_nick_func("", &this->get_dummy_channel()); } - for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + for (const auto& pair: this->channels) { - change_nick_func(it->first, it->second.get()); + change_nick_func(pair.first, pair.second.get()); } } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 8119201..7593029 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -26,9 +26,9 @@ class Bridge; class IrcClient: public TCPClientSocketHandler { public: - explicit IrcClient(std::shared_ptr& poller, const std::string& hostname, - const std::string& nickname, const std::string& username, - const std::string& realname, const std::string& user_hostname, + explicit IrcClient(std::shared_ptr& poller, std::string hostname, + std::string nickname, std::string username, + std::string realname, std::string user_hostname, Bridge& bridge); ~IrcClient(); diff --git a/src/main.cpp b/src/main.cpp index 76ab5d9..2fa72d5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,7 @@ #endif #include -#include +#include #ifdef USE_DATABASE # include #endif diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 9f5bcfb..9f62e36 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp index 4628703..7181c9d 100644 --- a/src/network/tcp_client_socket_handler.cpp +++ b/src/network/tcp_client_socket_handler.cpp @@ -256,6 +256,6 @@ std::string TCPClientSocketHandler::get_port() const bool TCPClientSocketHandler::match_port_pairt(const uint16_t local, const uint16_t remote) const { - const uint16_t remote_port = static_cast(std::stoi(this->port)); + const auto remote_port = static_cast(std::stoi(this->port)); return this->is_connected() && local == this->local_port && remote == remote_port; } diff --git a/src/network/tcp_server_socket.hpp b/src/network/tcp_server_socket.hpp index c511962..652b773 100644 --- a/src/network/tcp_server_socket.hpp +++ b/src/network/tcp_server_socket.hpp @@ -12,7 +12,6 @@ #include #include -#include template class TcpSocketServer: public SocketHandler diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 7eebae0..b5e5db1 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #ifdef BOTAN_FOUND diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index aa91dac..cff0039 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include #include #include diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp index 5077199..e35a659 100644 --- a/src/utils/timed_events.cpp +++ b/src/utils/timed_events.cpp @@ -1,22 +1,23 @@ +#include #include TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback, const std::string& name): + std::function callback, std::string name): time_point(std::move(time_point)), - callback(callback), + callback(std::move(callback)), repeat(false), repeat_delay(0), - name(name) + name(std::move(name)) { } TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, - std::function callback, const std::string& name): + std::function callback, std::string name): time_point(std::chrono::steady_clock::now() + duration), - callback(callback), + callback(std::move(callback)), repeat(true), repeat_delay(std::move(duration)), - name(name) + name(std::move(name)) { } diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp index 6e28206..393b38d 100644 --- a/src/utils/timed_events.hpp +++ b/src/utils/timed_events.hpp @@ -25,9 +25,9 @@ public: * An event the occurs only once, at the given time_point */ explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback, const std::string& name=""); + std::function callback, std::string name=""); explicit TimedEvent(std::chrono::milliseconds&& duration, - std::function callback, const std::string& name=""); + std::function callback, std::string name=""); explicit TimedEvent(TimedEvent&&) = default; TimedEvent& operator=(TimedEvent&&) = default; diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index 825cc92..e02bf35 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -1,11 +1,12 @@ +#include #include #include #include using namespace std::string_literals; -AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): - name(name), +AdhocCommand::AdhocCommand(std::vector&& callbacks, std::string name, const bool admin_only): + name(std::move(name)), callbacks(std::move(callbacks)), admin_only(admin_only) { diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp index ced4549..c00d9e6 100644 --- a/src/xmpp/adhoc_command.hpp +++ b/src/xmpp/adhoc_command.hpp @@ -17,7 +17,7 @@ class AdhocCommand { friend class AdhocSession; public: - AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only); + AdhocCommand(std::vector&& callbacks, std::string name, const bool admin_only); ~AdhocCommand() = default; AdhocCommand(const AdhocCommand&) = default; AdhocCommand(AdhocCommand&&) = default; diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index dc57eeb..b4b6a45 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -83,10 +83,8 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr& poller, const std::s void BiboumiComponent::shutdown() { - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - { - it->second->shutdown("Gateway shutdown"); - } + for (auto& pair: this->bridges) + pair.second->shutdown("Gateway shutdown"); } void BiboumiComponent::clean() @@ -696,8 +694,8 @@ Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid) std::vector BiboumiComponent::get_bridges() const { std::vector res; - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - res.push_back(it->second.get()); + for (const auto& bridge: this->bridges) + res.push_back(bridge.second.get()); return res; } diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index ba8d70b..19d1b55 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -53,7 +53,7 @@ std::string jidprep(const std::string& original) char local[max_jid_part_len] = {}; memcpy(local, jid.local.data(), std::min(max_jid_part_len, jid.local.size())); - Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, + auto rc = static_cast(::stringprep(local, max_jid_part_len, static_cast(0), stringprep_xmpp_nodeprep)); if (rc != STRINGPREP_OK) { diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index ac6ce9b..4999851 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include std::string xml_escape(const std::string& data) { -- cgit v1.2.3 From ccb4ee098f0416ab47a46650705dba6495e8bec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Apr 2017 18:53:12 +0200 Subject: Apply all the clang-tidy misc-* fixes --- src/irc/iid.cpp | 2 +- src/irc/irc_client.cpp | 4 ++-- src/network/credentials_manager.cpp | 2 +- src/network/tcp_socket_handler.cpp | 1 - src/utils/timed_events.cpp | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 63c7039..a63a1c3 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -9,7 +9,7 @@ constexpr char Iid::separator[]; Iid::Iid(std::string local, std::string server, Iid::Type type): - type(std::move(type)), + type(type), local(std::move(local)), server(std::move(server)) { diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 710ba15..1d025f2 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -382,7 +382,7 @@ void IrcClient::send_message(IrcMessage&& message) std::string res; if (!message.prefix.empty()) res += ":" + std::move(message.prefix) + " "; - res += std::move(message.command); + res += message.command; for (const std::string& arg: message.arguments) { if (arg.find(" ") != std::string::npos || @@ -889,7 +889,7 @@ void IrcClient::on_part(const IrcMessage& message) // channel pointer is now invalid channel = nullptr; } - this->bridge.send_muc_leave(iid, std::move(nick), std::move(txt), self); + this->bridge.send_muc_leave(iid, std::move(nick), txt, self); } } diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp index f9f8c94..0908a2f 100644 --- a/src/network/credentials_manager.cpp +++ b/src/network/credentials_manager.cpp @@ -98,7 +98,7 @@ bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector& poller): SocketHandler(poller, -1), diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp index e35a659..26ded82 100644 --- a/src/utils/timed_events.cpp +++ b/src/utils/timed_events.cpp @@ -3,7 +3,7 @@ TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, std::function callback, std::string name): - time_point(std::move(time_point)), + time_point(time_point), callback(std::move(callback)), repeat(false), repeat_delay(0), @@ -16,7 +16,7 @@ TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, time_point(std::chrono::steady_clock::now() + duration), callback(std::move(callback)), repeat(true), - repeat_delay(std::move(duration)), + repeat_delay(duration), name(std::move(name)) { } -- cgit v1.2.3 From be9c577de840c7f0dc08b9f5b9ba9bd522d0e2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Apr 2017 19:01:49 +0200 Subject: Apply all the clang-tidy performance-* fixes --- src/bridge/bridge.cpp | 6 +++--- src/bridge/bridge.hpp | 2 +- src/irc/irc_client.cpp | 4 ++-- src/network/credentials_manager.cpp | 2 +- src/network/credentials_manager.hpp | 2 +- src/xmpp/xmpp_component.cpp | 2 +- src/xmpp/xmpp_stanza.cpp | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4966b0d..591e947 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -988,16 +988,16 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c this->send_room_history(hostname, chan_name, resource); } -void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource) +void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource) { #ifdef USE_DATABASE const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name); const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value()); + chan_name.append(utils::empty_if_fixed_server("%" + hostname)); for (const auto& line: lines) { const auto seconds = line.date.value().timeStamp(); - this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), - line.body.value(), + this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(), this->user_jid + "/" + resource, seconds); } #endif diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 53d2136..f192545 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -157,7 +157,7 @@ public: * Send the MUC history to the user */ void send_room_history(const std::string& hostname, const std::string& chan_name); - void send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource); + void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource); /** * Send a MUC message from some participant */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1d025f2..1f562fe 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -385,7 +385,7 @@ void IrcClient::send_message(IrcMessage&& message) res += message.command; for (const std::string& arg: message.arguments) { - if (arg.find(" ") != std::string::npos || + if (arg.find(' ') != std::string::npos || (!arg.empty() && arg[0] == ':')) { res += " :" + arg; @@ -1080,7 +1080,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) { // That mode can also be of type B if it is present in the // prefix_to_mode map - for (const std::pair& pair: this->prefix_to_mode) + for (const auto& pair: this->prefix_to_mode) if (pair.second == c) { type = 1; diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp index 0908a2f..ea76627 100644 --- a/src/network/credentials_manager.cpp +++ b/src/network/credentials_manager.cpp @@ -44,7 +44,7 @@ const std::string& BasicCredentialsManager::get_trusted_fingerprint() const void check_tls_certificate(const std::vector& certs, const std::string& hostname, const std::string& trusted_fingerprint, - std::exception_ptr exc) + const std::exception_ptr& exc) { if (!trusted_fingerprint.empty() && !certs.empty() && diff --git a/src/network/credentials_manager.hpp b/src/network/credentials_manager.hpp index c463ad4..e7c247d 100644 --- a/src/network/credentials_manager.hpp +++ b/src/network/credentials_manager.hpp @@ -19,7 +19,7 @@ class TCPSocketHandler; */ void check_tls_certificate(const std::vector& certs, const std::string& hostname, const std::string& trusted_fingerprint, - std::exception_ptr exc); + const std::exception_ptr& exc); class BasicCredentialsManager: public Botan::Credentials_Manager { diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 8335c8a..35abbee 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -445,7 +445,7 @@ void XmppComponent::send_muc_leave(const std::string& muc_name, const std::strin presence["to"] = jid_to; presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; presence["type"] = "unavailable"; - const std::string message_str = std::get<0>(message); + const std::string& message_str = std::get<0>(message); XmlSubNode x(presence, "x"); x["xmlns"] = MUC_USER_NS; if (self) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 4999851..435f333 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -52,7 +52,7 @@ XmlNode::XmlNode(const std::string& name, XmlNode* parent): parent(parent) { // split the namespace and the name - auto n = name.rfind(":"); + auto n = name.rfind(':'); if (n == std::string::npos) this->name = name; else -- cgit v1.2.3 From 8a912ea3a3a74f6c4d2cb67adf3f60b4ec0a1c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Apr 2017 11:10:51 +0200 Subject: Apply a few clang-tidy cppcoreguidelines-* fixes --- src/main.cpp | 2 +- src/network/poller.cpp | 2 +- src/network/tcp_client_socket_handler.cpp | 6 +++--- src/utils/system.cpp | 2 +- src/xmpp/xmpp_parser.hpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 2fa72d5..2db89cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,7 @@ int main(int ac, char** av) // Block the signals we want to manage. They will be unblocked only during // the epoll_pwait or ppoll calls. This avoids some race conditions, // explained in man 2 pselect on linux - sigset_t mask; + sigset_t mask{}; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 9f62e36..ca49180 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -198,7 +198,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) static const size_t max_events = 12; struct epoll_event revents[max_events]; // Unblock all signals, only during the epoll_pwait call - sigset_t empty_signal_set; + sigset_t empty_signal_set{}; sigemptyset(&empty_signal_set); const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(), &empty_signal_set); diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp index 7181c9d..35f2446 100644 --- a/src/network/tcp_client_socket_handler.cpp +++ b/src/network/tcp_client_socket_handler.cpp @@ -35,7 +35,7 @@ void TCPClientSocketHandler::init_socket(const struct addrinfo* rp) // Convert the address from string format to a sockaddr that can be // used in bind() struct addrinfo* result; - struct addrinfo hints; + struct addrinfo hints{}; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; @@ -161,14 +161,14 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri this->local_port = static_cast(-1); if (rp->ai_family == AF_INET6) { - struct sockaddr_in6 a; + struct sockaddr_in6 a{}; socklen_t l = sizeof(a); if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) this->local_port = ntohs(a.sin6_port); } else if (rp->ai_family == AF_INET) { - struct sockaddr_in a; + struct sockaddr_in a{}; socklen_t l = sizeof(a); if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) this->local_port = ntohs(a.sin_port); diff --git a/src/utils/system.cpp b/src/utils/system.cpp index c0bee11..d821dec 100644 --- a/src/utils/system.cpp +++ b/src/utils/system.cpp @@ -9,7 +9,7 @@ namespace utils { std::string get_system_name() { - struct utsname uts; + struct utsname uts{}; const int res = ::uname(&uts); if (res == -1) { diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp index 9d67228..ec42f9a 100644 --- a/src/xmpp/xmpp_parser.hpp +++ b/src/xmpp/xmpp_parser.hpp @@ -106,7 +106,7 @@ private: /** * Expat structure. */ - XML_Parser parser; + XML_Parser parser{}; /** * The current depth in the XML document */ -- cgit v1.2.3 From 68d6b829402592d2d7a00e6e7b5013077aaa745c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 9 Apr 2017 23:03:35 +0200 Subject: Properly handle multiline topics fix #3254 --- src/bridge/bridge.cpp | 5 ++++- src/bridge/bridge.hpp | 2 +- src/utils/string.hpp | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 591e947..f263c3f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -684,9 +684,12 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: this->add_waiting_irc(std::move(cb)); } -void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) +void Bridge::set_channel_topic(const Iid& iid, std::string subject) { IrcClient* irc = this->get_irc_client(iid.get_server()); + std::string::size_type pos{0}; + while ((pos = subject.find('\n', pos)) != std::string::npos) + subject[pos] = ' '; irc->send_topic_command(iid.get_local(), subject); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f192545..1c89bd5 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -83,7 +83,7 @@ public: void send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid); - void set_channel_topic(const Iid& iid, const std::string& subject); + void set_channel_topic(const Iid& iid, std::string subject); void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os); void send_irc_ping_result(const Iid& iid, const std::string& id); diff --git a/src/utils/string.hpp b/src/utils/string.hpp index 84ba101..071ce2c 100644 --- a/src/utils/string.hpp +++ b/src/utils/string.hpp @@ -6,5 +6,3 @@ bool to_bool(const std::string& val); std::vector cut(const std::string& val, const std::size_t size); - - -- cgit v1.2.3 From 3a8203c366d46ec2937a601868f5e2ed591c923e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Apr 2017 17:07:39 +0200 Subject: Handle the RSM "max" value in the MAM requests fix #3255 --- src/xmpp/biboumi_component.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index b4b6a45..8b2e541 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -619,7 +619,15 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) } } } - const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end); + const XmlNode* set = query->get_child("set", RSM_NS); + int limit = -1; + if (set) + { + const XmlNode* max = set->get_child("max", RSM_NS); + if (max) + limit = std::atoi(max->get_inner().data()); + } + const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); for (const db::MucLogLine& line: lines) { if (!line.nick.value().empty()) -- cgit v1.2.3 From 32384047537ed7c63cf3099b247777ed6035af49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 17 Apr 2017 14:45:19 +0200 Subject: =?UTF-8?q?Avoid=20adding=20more=20that=20one=20=E2=80=9CXMPP=20re?= =?UTF-8?q?connection=E2=80=9D=20timed=20event=20at=20the=20same=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a semblance of infinite and busy loop, that could occur if biboumi’s poller is woken up multiple times while the XMPP server is not reachable. --- src/main.cpp | 5 +++-- src/utils/timed_events.hpp | 5 +++++ src/utils/timed_events_manager.cpp | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 2db89cc..1a9b065 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -179,19 +179,20 @@ int main(int ac, char** av) { if (xmpp_component->ever_auth) { + static const std::string reconnect_name{"XMPP reconnection"}; if (xmpp_component->first_connection_try == true) { // immediately re-try to connect xmpp_component->reset(); xmpp_component->start(); } - else + else if (!TimedEventsManager::instance().find_event(reconnect_name)) { // Re-connecting failed, we now try only each few seconds auto reconnect_later = [xmpp_component]() { xmpp_component->reset(); xmpp_component->start(); }; - TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, "XMPP reconnection"); + TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, reconnect_name); TimedEventsManager::instance().add_event(std::move(event)); } } diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp index 393b38d..fa0fc50 100644 --- a/src/utils/timed_events.hpp +++ b/src/utils/timed_events.hpp @@ -125,6 +125,11 @@ public: * Return the number of managed events. */ std::size_t size() const; + /** + * Return a pointer to the first event with the given name. If none + * is found, returns nullptr. + */ + const TimedEvent* find_event(const std::string& name) const; private: std::vector events; diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp index 67d61fe..75e6338 100644 --- a/src/utils/timed_events_manager.cpp +++ b/src/utils/timed_events_manager.cpp @@ -1,5 +1,7 @@ #include +#include + TimedEventsManager& TimedEventsManager::instance() { static TimedEventsManager inst; @@ -67,7 +69,19 @@ std::size_t TimedEventsManager::cancel(const std::string& name) return res; } + + std::size_t TimedEventsManager::size() const { return this->events.size(); } + +const TimedEvent* TimedEventsManager::find_event(const std::string& name) const +{ + const auto it = std::find_if(this->events.begin(), this->events.end(), [&name](const TimedEvent& o) { + return o.get_name() == name; + }); + if (it == this->events.end()) + return nullptr; + return &*it; +} -- cgit v1.2.3 From 54fa739d6b5d2cc0b3704eda32c7abac47708b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Apr 2017 17:53:44 +0200 Subject: Limit of 100 MAM messages, if no other limit has been set by the client fix #3256 --- src/xmpp/biboumi_component.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 8b2e541..c808eec 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -627,6 +627,12 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) if (max) limit = std::atoi(max->get_inner().data()); } + // 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()) + { + limit = 100; + } const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); for (const db::MucLogLine& line: lines) { -- cgit v1.2.3 From 727d887d7bc8a17b88402ae63e0569084a24a84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Apr 2017 00:24:59 +0200 Subject: Fix wrong JID computing when sending iq ping or version in fixed mode fix #3259 --- src/bridge/bridge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f263c3f..69e8c35 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1048,7 +1048,7 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string& { const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), + this->xmpp.send_iq_version_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname), this->user_jid + "/" + *resources.begin()); } @@ -1061,7 +1061,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Forward to the first resource (arbitrary, based on the “order” of the std::set) only const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), + this->xmpp.send_ping_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } -- cgit v1.2.3 From 51696c091cc7058b05b33f1085b1246f3b5dc59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 19 Apr 2017 23:33:07 +0200 Subject: Make sure the channel is joined before trying to leave it fix #3243 --- src/bridge/bridge.cpp | 12 ++++++------ src/bridge/bridge.hpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 69e8c35..589bd03 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -425,7 +425,6 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con return ; IrcChannel* channel = irc->get_channel(iid.get_local()); - auto nick = channel->get_self()->nick; const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) @@ -447,9 +446,9 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con else irc->send_part_command(iid.get_local(), status_message); } - else + else if (channel->joined) { - this->send_muc_leave(iid, std::move(nick), "", true, resource); + this->send_muc_leave(iid, channel->get_self()->nick, "", true, resource); } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID @@ -457,8 +456,8 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con } else { - if (channel) - this->send_muc_leave(iid, std::move(nick), + if (channel && channel->joined) + this->send_muc_leave(iid, channel->get_self()->nick, "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", true, resource); this->remove_resource_from_chan(key, resource); @@ -876,7 +875,8 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } -void Bridge::send_muc_leave(const Iid &iid, std::string&& nick, const std::string& message, const bool self, +void Bridge::send_muc_leave(const Iid& iid, const std::string& nick, + const std::string& message, const bool self, const std::string& resource) { if (!resource.empty()) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1c89bd5..d8facad 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -169,7 +169,7 @@ public: /** * Send an unavailable presence from this participant */ - void send_muc_leave(const Iid& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource = ""); + void send_muc_leave(const Iid& iid, const std::string& nick, const std::string& message, const bool self, const std::string& resource = ""); /** * Send presences to indicate that an user old_nick (ourself if self == * true) changed his nick to new_nick. The user_mode is needed because -- cgit v1.2.3 From e3ee824e5c5548c13605e4f2e2ded9491c1c1479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Apr 2017 10:09:35 +0200 Subject: Revert "Cancel the IRC server linger timer when we try to-rejoin a channel on it" This reverts commit 45f7396c8d30ed37570c4ecdaa886388f9beba3e. --- src/bridge/bridge.cpp | 7 ------- src/bridge/bridge.hpp | 1 - 2 files changed, 8 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 589bd03..0a9a412 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -170,7 +170,6 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const const std::string& resource) { const auto& hostname = iid.get_server(); - this->cancel_linger_timer(hostname); IrcClient* irc = this->make_irc_client(hostname, nickname); this->add_resource_to_server(hostname, resource); auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource); @@ -1264,9 +1263,3 @@ void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname) }, event_name); TimedEventsManager::instance().add_event(std::move(event)); } - -void Bridge::cancel_linger_timer(const std::string& irc_hostname) -{ - const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid(); - TimedEventsManager::instance().cancel(event_name); -} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index d8facad..ce9c605 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -241,7 +241,6 @@ public: * configured linger time is expired. */ void quit_or_start_linger_timer(const std::string& irc_hostname); - void cancel_linger_timer(const std::string& irc_hostname); private: /** -- cgit v1.2.3 From 5ef674c4862f1ad265e76ea6fabc20e180871243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Apr 2017 10:12:00 +0200 Subject: Revert "Add a linger_time configuration option on IRC servers" This reverts commit 5d801ddcd025f68d2ec91edf0462091a32c779c1. --- src/bridge/bridge.cpp | 22 +--------------------- src/bridge/bridge.hpp | 5 ----- src/xmpp/biboumi_adhoc_commands.cpp | 14 -------------- 3 files changed, 1 insertion(+), 40 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 0a9a412..e362822 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -12,7 +12,6 @@ #include #include "result_set_management.hpp" #include -#include using namespace std::string_literals; @@ -892,7 +891,7 @@ void Bridge::send_muc_leave(const Iid& iid, const std::string& nick, } IrcClient* irc = this->find_irc_client(iid.get_server()); if (self && irc && irc->number_of_joined_channels() == 0) - this->quit_or_start_linger_timer(iid.get_server()); + irc->send_quit_command(""); } void Bridge::send_nick_change(Iid&& iid, @@ -1244,22 +1243,3 @@ void Bridge::set_record_history(const bool val) this->record_history = val; } #endif - -void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname) -{ -#ifdef USE_DATABASE - auto options = Database::get_irc_server_options(this->get_bare_jid(), - irc_hostname); - const auto timeout = std::chrono::seconds(options.lingerTime.value()); -#else - const auto timeout = 0s; -#endif - - const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid(); - TimedEvent event(std::chrono::steady_clock::now() + timeout, [this, irc_hostname]() { - IrcClient* irc = this->find_irc_client(irc_hostname); - if (irc) - irc->send_quit_command(""); - }, event_name); - TimedEventsManager::instance().add_event(std::move(event)); -} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index ce9c605..033291c 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -236,11 +236,6 @@ public: #ifdef USE_DATABASE void set_record_history(const bool val); #endif - /** - * Start a timer that will send a QUIT command after the - * configured linger time is expired. - */ - void quit_or_start_linger_timer(const std::string& irc_hostname); private: /** diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 85f945d..9432697 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -319,16 +319,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); } - - XmlSubNode linger_time(x, "field"); - linger_time["var"] = "linger_time"; - linger_time["type"] = "text-single"; - linger_time["desc"] = "The number of seconds to wait before sending a QUIT command, after the last channel on that server has been left."; - linger_time["label"] = "Linger time"; - { - XmlSubNode linger_time_value(linger_time, "value"); - linger_time_value.set_inner(std::to_string(options.lingerTime.value())); - } } void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -408,10 +398,6 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com value && !value->get_inner().empty()) options.encodingIn = value->get_inner(); - else if (field->get_tag("var") == "linger_time" && - value && !value->get_inner().empty()) - options.lingerTime = value->get_inner(); - } options.update(); -- cgit v1.2.3 From 69fd6cc4c3b88443ce98ae32e34909654feb1e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Apr 2017 19:14:59 +0200 Subject: Explicitely init the msghdr fields, to be compatible with any implementation --- src/network/tcp_socket_handler.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 1bb8bf3..789a4a7 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -125,9 +125,14 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) void TCPSocketHandler::on_send() { struct iovec msg_iov[UIO_FASTIOV] = {}; - struct msghdr msg{nullptr, 0, - msg_iov, - 0, nullptr, 0, 0}; + struct msghdr msg; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = msg_iov; + msg.msg_iovlen = 0; + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = 0; for (const std::string& s: this->out_buf) { // unconsting the content of s is ok, sendmsg will never modify it -- cgit v1.2.3 From cf87cf089251eddf2c33322e07b0cde9f70ec24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Apr 2017 15:44:46 +0200 Subject: Better way to init the msghdr fields --- src/network/tcp_socket_handler.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src') diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 789a4a7..02265ec 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -125,14 +125,9 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) void TCPSocketHandler::on_send() { struct iovec msg_iov[UIO_FASTIOV] = {}; - struct msghdr msg; - msg.msg_name = nullptr; - msg.msg_namelen = 0; + struct msghdr msg{}; msg.msg_iov = msg_iov; msg.msg_iovlen = 0; - msg.msg_control = nullptr; - msg.msg_controllen = 0; - msg.msg_flags = 0; for (const std::string& s: this->out_buf) { // unconsting the content of s is ok, sendmsg will never modify it -- cgit v1.2.3 From eac144acdaca02f018bddde5f623fba3e8cd4ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Apr 2017 11:19:36 +0200 Subject: Configuration options can be overridden by setting env values --- src/config/config.cpp | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/config/config.cpp b/src/config/config.cpp index 0db5751..0f3d639 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,10 +1,15 @@ #include +#include #include #include #include +using namespace std::string_literals; + +extern char** environ; + std::string Config::filename{}; std::map Config::values{}; std::vector Config::callbacks{}; @@ -71,21 +76,40 @@ bool Config::read_conf(const std::string& name) Config::clear(); + auto parse_line = [](const std::string& line, const bool env) + { + static const auto env_option_prefix = "BIBOUMI_"s; + + if (line == "" || line[0] == '#') + return; + size_t pos = line.find('='); + if (pos == std::string::npos) + return; + std::string option = line.substr(0, pos); + std::string value = line.substr(pos+1); + if (env) + { + auto a = option.substr(0, env_option_prefix.size()); + if (a == env_option_prefix) + option = utils::tolower(option.substr(env_option_prefix.size())); + else + return; + } + Config::values[option] = value; + }; + std::string line; - size_t pos; - std::string option; - std::string value; while (file.good()) { std::getline(file, line); - if (line == "" || line[0] == '#') - continue ; - pos = line.find('='); - if (pos == std::string::npos) - continue ; - option = line.substr(0, pos); - value = line.substr(pos+1); - Config::values[option] = value; + parse_line(line, false); + } + + char** env_line = environ; + while (*env_line) + { + parse_line(*env_line, true); + env_line++; } return true; } -- cgit v1.2.3 From f588ce071eb99ce80fd25f899679e902214606cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Apr 2017 22:47:25 +0200 Subject: Group simultaneous JOINs into a single command, to avoid flooding We still split the JOINs with a key and the ones without --- src/irc/irc_client.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1f562fe..ea5afd2 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -846,8 +846,36 @@ void IrcClient::on_welcome_message(const IrcMessage& message) // Install a repeated events to regularly send a PING TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), "PING"s + this->hostname + this->bridge.get_jid())); + std::string channels{}; + std::string channels_with_key{}; + std::string keys{}; + for (const auto& tuple: this->channels_to_join) - this->send_join_command(std::get<0>(tuple), std::get<1>(tuple)); + { + const auto& chan = std::get<0>(tuple); + const auto& key = std::get<1>(tuple); + if (chan.empty()) + continue; + if (!key.empty()) + { + if (!keys.empty()) + keys += ","; + keys += key; + if (!channels_with_key.empty()) + channels_with_key += ","; + channels_with_key += chan; + } + else + { + if (!channels.empty()) + channels += ","; + channels += chan; + } + } + if (!channels.empty()) + this->send_join_command(channels, {}); + if (!channels_with_key.empty()) + this->send_join_command(channels_with_key, keys); this->channels_to_join.clear(); // Indicate that the dummy channel is joined as well, if needed if (this->dummy_channel.joining) -- cgit v1.2.3 From 3af9d0ac67377c5b7535415696d73bd470aa08f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Apr 2017 22:55:24 +0200 Subject: =?UTF-8?q?Make=20sure=20we=20don=E2=80=99t=20exceed=20512=20bytes?= =?UTF-8?q?=20when=20grouping=20JOINs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/irc/irc_client.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ea5afd2..90fad75 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -858,6 +858,12 @@ void IrcClient::on_welcome_message(const IrcMessage& message) continue; if (!key.empty()) { + if (keys.size() + channels_with_key.size() >= 300) + { // Arbitrary size, to make sure we never send more than 512 + this->send_join_command(channels_with_key, keys); + channels_with_key.clear(); + keys.clear(); + } if (!keys.empty()) keys += ","; keys += key; @@ -867,6 +873,11 @@ void IrcClient::on_welcome_message(const IrcMessage& message) } else { + if (channels.size() >= 300) + { // Arbitrary size, to make sure we never send more than 512 + this->send_join_command(channels, {}); + channels.clear(); + } if (!channels.empty()) channels += ","; channels += chan; -- cgit v1.2.3 From 7b3e0e0cf3eddd3537455a3605b04a48ee663f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 30 Apr 2017 15:04:40 +0200 Subject: =?UTF-8?q?Make=20botan=E2=80=99s=20policy=20configurable=20from?= =?UTF-8?q?=20a=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #3244 --- src/network/tcp_socket_handler.cpp | 14 ++++++----- src/network/tcp_socket_handler.hpp | 17 ++------------ src/network/tls_policy.cpp | 48 ++++++++++++++++++++++++++++++++++++++ src/network/tls_policy.hpp | 28 ++++++++++++++++++++++ src/utils/dirname.cpp | 16 +++++++++++++ src/utils/dirname.hpp | 6 +++++ src/utils/xdg.hpp | 2 -- 7 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 src/network/tls_policy.cpp create mode 100644 src/network/tls_policy.hpp create mode 100644 src/utils/dirname.cpp create mode 100644 src/utils/dirname.hpp (limited to 'src') diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 02265ec..1bd5315 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -14,6 +14,8 @@ #ifdef BOTAN_FOUND # include # include +# include +# include namespace { @@ -22,11 +24,6 @@ namespace static Botan::AutoSeeded_RNG rng{}; return rng; } - BiboumiTLSPolicy& get_policy() - { - static BiboumiTLSPolicy policy{}; - return policy; - } Botan::TLS::Session_Manager_In_Memory& get_session_manager() { static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; @@ -233,6 +230,11 @@ void TCPSocketHandler::consume_in_buffer(const std::size_t size) void TCPSocketHandler::start_tls(const std::string& address, const std::string& port) { Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port)); + auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename())); + if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/') + policy_directory += '/'; + this->policy.load(policy_directory + "policy.txt"); + this->policy.load(policy_directory + address + ".policy.txt"); this->tls = std::make_unique( # if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) *this, @@ -242,7 +244,7 @@ void TCPSocketHandler::start_tls(const std::string& address, const std::string& [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); }, [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); }, # endif - get_session_manager(), this->credential_manager, get_policy(), + get_session_manager(), this->credential_manager, this->policy, get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version()); } diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index ba23861..f68698e 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -23,21 +23,7 @@ # include # include # include - -class BiboumiTLSPolicy: public Botan::TLS::Policy -{ -public: -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,33) - bool use_ecc_point_compression() const override - { - return true; - } - bool require_cert_revocation_info() const override - { - return false; - } -# endif -}; +# include # if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) # define BOTAN_TLS_CALLBACKS_OVERRIDE override final @@ -230,6 +216,7 @@ protected: protected: BasicCredentialsManager credential_manager; private: + BiboumiTLSPolicy policy; /** * We use a unique_ptr because we may not want to create the object at * all. The Botan::TLS::Client object generates a handshake message and diff --git a/src/network/tls_policy.cpp b/src/network/tls_policy.cpp new file mode 100644 index 0000000..5439397 --- /dev/null +++ b/src/network/tls_policy.cpp @@ -0,0 +1,48 @@ +#include "biboumi.h" + +#ifdef BOTAN_FOUND + +#include + +#include + +#include +#include + +bool BiboumiTLSPolicy::load(const std::string& filename) +{ + std::ifstream is(filename.data()); + if (is) + { + try { + this->load(is); + log_info("Successfully loaded policy file: ", filename); + return true; + } catch (const Botan::Exception& e) { + log_error("Failed to parse policy_file ", filename, ": ", e.what()); + return false; + } + } + log_info("Could not open policy file: ", filename); + return false; +} + +void BiboumiTLSPolicy::load(std::istream& is) +{ + const auto dict = Botan::read_cfg(is); + for (const auto& pair: dict) + { + // Workaround for options that are not overridden in Botan::TLS::Text_Policy + if (pair.first == "require_cert_revocation_info") + this->req_cert_revocation_info = !(pair.second == "0" || utils::tolower(pair.second) == "false"); + else + this->set(pair.first, pair.second); + } +} + +bool BiboumiTLSPolicy::require_cert_revocation_info() const +{ + return this->req_cert_revocation_info; +} + +#endif diff --git a/src/network/tls_policy.hpp b/src/network/tls_policy.hpp new file mode 100644 index 0000000..29fd2b3 --- /dev/null +++ b/src/network/tls_policy.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "biboumi.h" + +#ifdef BOTAN_FOUND + +#include + +class BiboumiTLSPolicy: public Botan::TLS::Text_Policy +{ +public: + BiboumiTLSPolicy(): + Botan::TLS::Text_Policy({}) + {} + bool load(const std::string& filename); + void load(std::istream& iss); + + BiboumiTLSPolicy(const BiboumiTLSPolicy &) = delete; + BiboumiTLSPolicy(BiboumiTLSPolicy &&) = delete; + BiboumiTLSPolicy &operator=(const BiboumiTLSPolicy &) = delete; + BiboumiTLSPolicy &operator=(BiboumiTLSPolicy &&) = delete; + + bool require_cert_revocation_info() const override; +protected: + bool req_cert_revocation_info{true}; +}; + +#endif diff --git a/src/utils/dirname.cpp b/src/utils/dirname.cpp new file mode 100644 index 0000000..71c9c38 --- /dev/null +++ b/src/utils/dirname.cpp @@ -0,0 +1,16 @@ +#include + +namespace utils +{ + std::string dirname(const std::string filename) + { + if (filename.empty()) + return "./"; + if (filename == ".." || filename == ".") + return filename; + auto pos = filename.rfind('/'); + if (pos == std::string::npos) + return "./"; + return filename.substr(0, pos + 1); + } +} diff --git a/src/utils/dirname.hpp b/src/utils/dirname.hpp new file mode 100644 index 0000000..c1df81b --- /dev/null +++ b/src/utils/dirname.hpp @@ -0,0 +1,6 @@ +#include + +namespace utils +{ +std::string dirname(const std::string filename); +} diff --git a/src/utils/xdg.hpp b/src/utils/xdg.hpp index 56e11da..7be6922 100644 --- a/src/utils/xdg.hpp +++ b/src/utils/xdg.hpp @@ -10,5 +10,3 @@ */ std::string xdg_config_path(const std::string& filename); std::string xdg_data_path(const std::string& filename); - - -- cgit v1.2.3 From 0a8a77e64ce4c314d8e6fa9eda8fc47f8cdef080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 2 May 2017 14:46:03 +0200 Subject: Fix a segmentation fault when connecting to a server without a port fix #3260 --- src/irc/irc_client.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 90fad75..31d6278 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -183,6 +183,11 @@ void IrcClient::start() { if (this->is_connecting() || this->is_connected()) return; + if (this->ports_to_try.empty()) + { + this->bridge.send_xmpp_message(this->hostname, "", "Can not connect to IRC server: no port specified."); + return; + } std::string port; bool tls; std::tie(port, tls) = this->ports_to_try.top(); -- cgit v1.2.3 From da701069dc9b607ce2eee300519feb49b31901de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 8 May 2017 18:47:02 +0200 Subject: Little fix and cleanup in the channels list code --- src/bridge/bridge.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e362822..11fd860 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -478,7 +478,7 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, c void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, ResultSetInfo rs_info) { - auto& list = channel_list_cache[iid.get_server()]; + auto& list = this->channel_list_cache[iid.get_server()]; // We fetch the list from the IRC server only if we have a complete // cached list that needs to be invalidated (that is, when the request @@ -501,7 +501,7 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq if (irc_hostname != iid.get_server()) return false; - auto& list = channel_list_cache[iid.get_server()]; + auto& list = this->channel_list_cache[iid.get_server()]; if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER") @@ -579,7 +579,7 @@ bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const R const std::string& id, const std::string& to_jid, const std::string& from) { auto begin = channel_list.channels.begin(); - auto end = channel_list.channels.begin(); + auto end = channel_list.channels.end(); if (channel_list.complete) { begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) -- cgit v1.2.3 From 984d71838ec98a94101804e48a4536c64d75602c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 8 May 2017 23:01:21 +0200 Subject: Limit the number of rooms sent by default in the disco#items response fix #3219 --- src/xmpp/biboumi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index c808eec..ad34ace 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -503,6 +503,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) rs_info.max = std::atoi(max->get_inner().data()); } + if (rs_info.max == -1) + rs_info.max = 100; bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info)); stanza_error.disable(); } -- cgit v1.2.3 From f7d4db4812c346ed2624a5885326408b4f794f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 8 May 2017 23:02:14 +0200 Subject: Remove a useless debug log --- src/bridge/bridge.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 11fd860..f2d782c 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -559,7 +559,6 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq { auto& list = channel_list_cache[iid.get_server()]; const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); - log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res); return res; } else if (message.command == "323" || message.command == "RPL_LISTEND") -- cgit v1.2.3 From f7e4adb10bff1c278a8543b230b10881ff3799fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 9 May 2017 15:46:20 +0200 Subject: Avoid any potential int overflow --- src/network/poller.cpp | 6 +++++- src/network/tcp_socket_handler.cpp | 5 +++-- src/xmpp/xmpp_component.cpp | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/network/poller.cpp b/src/network/poller.cpp index ca49180..0f02cc5 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -200,7 +200,11 @@ int Poller::poll(const std::chrono::milliseconds& timeout) // Unblock all signals, only during the epoll_pwait call sigset_t empty_signal_set{}; sigemptyset(&empty_signal_set); - const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(), + + int real_timeout = std::numeric_limits::max(); + if (timeout.count() < real_timeout) // Just avoid any potential int overflow + real_timeout = static_cast(timeout.count()); + const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, real_timeout, &empty_signal_set); if (nb_events == -1) { diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 1bd5315..1049375 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -227,9 +227,10 @@ void TCPSocketHandler::consume_in_buffer(const std::size_t size) } #ifdef BOTAN_FOUND -void TCPSocketHandler::start_tls(const std::string& address, const std::string& port) +void TCPSocketHandler::start_tls(const std::string& address, const std::string& port_string) { - Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port)); + auto port = std::min(std::stoul(port_string), static_cast(std::numeric_limits::max())); + Botan::TLS::Server_Information server_info(address, "irc", static_cast(port)); auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename())); if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/') policy_directory += '/'; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 35abbee..b138ed9 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -112,17 +112,20 @@ void XmppComponent::on_connection_close(const std::string& error) void XmppComponent::parse_in_buffer(const size_t size) { + // in_buf.size, or size, cannot be bigger than our read-size (4096) so it’s safe + // to cast. + 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->parser.feed(this->in_buf.data(), static_cast(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); + this->parser.parse(static_cast(size), false); } } -- cgit v1.2.3 From da55060840631bb63978a67270066b009b9c9270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 10 May 2017 16:48:51 +0200 Subject: Improve the handling of the biboudb.hpp dependencies --- src/network/credentials_manager.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp index ea76627..f93a366 100644 --- a/src/network/credentials_manager.cpp +++ b/src/network/credentials_manager.cpp @@ -7,10 +7,6 @@ #include #include -#ifdef USE_DATABASE -# include -#endif - /** * TODO find a standard way to find that out. */ -- cgit v1.2.3 From 8cf0b833c47314ada66e6a25bbdb9a2178e096d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 11 May 2017 21:15:43 +0200 Subject: Make the IRC channel configuration form available from the MUC config fix #3250 --- src/xmpp/biboumi_adhoc_commands.cpp | 83 +++++++++++++++++++++++-------------- src/xmpp/biboumi_adhoc_commands.hpp | 3 ++ src/xmpp/biboumi_component.cpp | 56 +++++++++++++++++++++++++ src/xmpp/biboumi_component.hpp | 3 ++ src/xmpp/xmpp_component.hpp | 1 + 5 files changed, 115 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 9432697..ab28cfd 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -418,11 +418,18 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); + + insert_irc_channel_configuration_form(command_node, owner, target); +} + +void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, const Jid& target) +{ const Iid iid(target.local, {}); - auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain, + + auto options = Database::get_irc_channel_options_with_server_default(requester.local + "@" + requester.domain, iid.get_server(), iid.get_local()); - XmlSubNode x(command_node, "jabber:x:data:x"); + XmlSubNode x(node, "jabber:x:data:x"); x["type"] = "form"; XmlSubNode title(x, "title"); title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server()); @@ -468,43 +475,57 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) { - const XmlNode* x = command_node.get_child("x", "jabber:x:data"); + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + if (handle_irc_channel_configuration_form(command_node, owner, target)) + { + command_node.delete_all_children(); + XmlSubNode note(command_node, "note"); + note["type"] = "info"; + note.set_inner("Configuration successfully applied."); + } + else + { + XmlSubNode error(command_node, ADHOC_NS":error"); + error["type"] = "modify"; + XmlSubNode condition(error, STANZA_NS":bad-request"); + session.terminate(); + } +} + +bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target) +{ + const XmlNode* x = node.get_child("x", "jabber:x:data"); if (x) { - const Jid owner(session.get_owner_jid()); - const Jid target(session.get_target_jid()); - const Iid iid(target.local, {}); - auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain, - iid.get_server(), iid.get_local()); - for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + if (x->get_tag("type") == "submit") { - const XmlNode* value = field->get_child("value", "jabber:x:data"); - - if (field->get_tag("var") == "encoding_out" && - value && !value->get_inner().empty()) - options.encodingOut = value->get_inner(); + const Iid iid(target.local, {}); + auto options = Database::get_irc_channel_options(requester.local + "@" + requester.domain, + iid.get_server(), iid.get_local()); + for (const XmlNode *field: x->get_children("field", "jabber:x:data")) + { + const XmlNode *value = field->get_child("value", "jabber:x:data"); - else if (field->get_tag("var") == "encoding_in" && - value && !value->get_inner().empty()) - options.encodingIn = value->get_inner(); + if (field->get_tag("var") == "encoding_out" && + value && !value->get_inner().empty()) + options.encodingOut = value->get_inner(); - else if (field->get_tag("var") == "persistent" && - value) - options.persistent = to_bool(value->get_inner()); - } + else if (field->get_tag("var") == "encoding_in" && + value && !value->get_inner().empty()) + options.encodingIn = value->get_inner(); - options.update(); + else if (field->get_tag("var") == "persistent" && + value) + options.persistent = to_bool(value->get_inner()); + } - command_node.delete_all_children(); - XmlSubNode note(command_node, "note"); - note["type"] = "info"; - note.set_inner("Configuration successfully applied."); - return; + options.update(); + } + return true; } - XmlSubNode error(command_node, ADHOC_NS":error"); - error["type"] = "modify"; - XmlSubNode condition(error, STANZA_NS":bad-request"); - session.terminate(); + return false; } #endif // USE_DATABASE diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index b5fce61..7d29cc2 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -4,6 +4,7 @@ #include #include #include +#include class XmppComponent; @@ -17,7 +18,9 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, const Jid& target); void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target); void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ad34ace..ca3a887 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -392,6 +392,11 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (this->handle_mam_request(stanza)) stanza_error.disable(); } + else if ((query = stanza.get_child("query", MUC_OWNER_NS))) + { + if (this->handle_room_configuration_form(*query, from, to, id)) + stanza_error.disable(); + } #endif } else if (type == "get") @@ -529,6 +534,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } stanza_error.disable(); } +#ifdef USE_DATABASE + else if ((query = stanza.get_child("query", MUC_OWNER_NS))) + { + if (this->handle_room_configuration_form_request(from, to, id)) + stanza_error.disable(); + } +#endif } else if (type == "result") { @@ -679,6 +691,50 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con this->send_stanza(message); } +bool BiboumiComponent::handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id) +{ + Iid iid(to.local, {'#', '&'}); + + if (iid.type != Iid::Type::Channel) + return false; + + Stanza iq("iq"); + { + iq["from"] = to.full(); + iq["to"] = from; + iq["id"] = id; + iq["type"] = "result"; + XmlSubNode query(iq, "query"); + query["xmlns"] = MUC_OWNER_NS; + Jid requester(from); + insert_irc_channel_configuration_form(query, requester, to); + } + this->send_stanza(iq); + return true; +} + +bool BiboumiComponent::handle_room_configuration_form(const XmlNode& query, const std::string &from, const Jid &to, const std::string &id) +{ + Iid iid(to.local, {'#', '&'}); + + if (iid.type != Iid::Type::Channel) + return false; + + Jid requester(from); + if (!handle_irc_channel_configuration_form(query, requester, to)) + return false; + + Stanza iq("iq"); + iq["type"] = "result"; + iq["from"] = to.full(); + iq["to"] = from; + iq["id"] = id; + + this->send_stanza(iq); + + return true; +} + #endif Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index f416dac..ac9bde4 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -2,6 +2,7 @@ #include +#include #include @@ -97,6 +98,8 @@ public: bool handle_mam_request(const Stanza& stanza); void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, const std::string& queryid); + bool handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id); + bool handle_room_configuration_form(const XmlNode& query, const std::string& from, const Jid& to, const std::string& id); #endif /** diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 8eabaf6..ebe3ec8 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -17,6 +17,7 @@ #define MUC_NS "http://jabber.org/protocol/muc" #define MUC_USER_NS MUC_NS"#user" #define MUC_ADMIN_NS MUC_NS"#admin" +#define MUC_OWNER_NS MUC_NS"#owner" #define DISCO_NS "http://jabber.org/protocol/disco" #define DISCO_ITEMS_NS DISCO_NS"#items" #define DISCO_INFO_NS DISCO_NS"#info" -- cgit v1.2.3 From bb150d587f080af38a74f2420457f1e0b2606a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 May 2017 00:30:07 +0200 Subject: Redirect welcome NOTICE to their channel, instead of sending a global one fix #3236 --- src/irc/irc_client.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 31d6278..caebb83 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -544,9 +544,18 @@ void IrcClient::forward_server_message(const IrcMessage& message) void IrcClient::on_notice(const IrcMessage& message) { std::string from = message.prefix; - const std::string to = message.arguments[0]; + std::string to = message.arguments[0]; const std::string body = message.arguments[1]; + // Handle notices starting with [#channame] as if they were sent to that channel + if (body.size() > 3 && body[0] == '[') + { + const auto chan_prefix = body[1]; + auto end = body.find(']'); + if (this->chantypes.find(chan_prefix) != this->chantypes.end() && end != std::string::npos) + to = body.substr(1, end - 1); + } + if (!body.empty() && body[0] == '\01' && body[body.size() - 1] == '\01') // Do not forward the notice to the user if it's a CTCP command return ; -- cgit v1.2.3 From 3079c38d2d6ad418d2591cc7f910c588dfebd279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 May 2017 16:00:32 +0200 Subject: Refactor the channel::self to point at the existing user This way, the user is always up to date, instead of being a duplicate out of sync. fix #3258 --- src/irc/irc_channel.cpp | 24 ++++++++++++++++++------ src/irc/irc_channel.hpp | 5 +++-- src/irc/irc_client.cpp | 26 +++++++++++++------------- 3 files changed, 34 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 40d7f54..53043c7 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -1,21 +1,25 @@ #include #include -void IrcChannel::set_self(const std::string& name) +void IrcChannel::set_self(IrcUser* user) { - this->self = std::make_unique(name); + this->self = user; } IrcUser* IrcChannel::add_user(const std::string& name, const std::map& prefix_to_mode) { - this->users.emplace_back(std::make_unique(name, prefix_to_mode)); + auto new_user = std::make_unique(name, prefix_to_mode); + auto old_user = this->find_user(new_user->nick); + if (old_user) + return old_user; + this->users.emplace_back(std::move(new_user)); return this->users.back().get(); } IrcUser* IrcChannel::get_self() const { - return this->self.get(); + return this->self; } IrcUser* IrcChannel::find_user(const std::string& name) const @@ -32,19 +36,27 @@ IrcUser* IrcChannel::find_user(const std::string& name) const void IrcChannel::remove_user(const IrcUser* user) { const auto nick = user->nick; + const bool is_self = (user == this->self); const auto it = std::find_if(this->users.begin(), this->users.end(), [nick](const std::unique_ptr& u) { return nick == u->nick; }); if (it != this->users.end()) - this->users.erase(it); + { + this->users.erase(it); + if (is_self) + { + this->self = nullptr; + this->joined = false; + } + } } void IrcChannel::remove_all_users() { this->users.clear(); - this->self.reset(); + this->self = nullptr; } DummyIrcChannel::DummyIrcChannel(): diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 7c269b9..8f85edb 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -27,7 +27,7 @@ public: bool parting{false}; std::string topic{}; std::string topic_author{}; - void set_self(const std::string& name); + void set_self(IrcUser* user); IrcUser* get_self() const; IrcUser* add_user(const std::string& name, const std::map& prefix_to_mode); @@ -38,7 +38,8 @@ public: { return this->users; } protected: - std::unique_ptr self{}; + // Pointer to one IrcUser stored in users + IrcUser* self{nullptr}; std::vector> users{}; }; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index caebb83..0e3926a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -642,15 +642,18 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) std::vector nicks = utils::split(message.arguments[3], ' '); for (const std::string& nick: nicks) { - const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); - if (user->nick != channel->get_self()->nick) + // Just create this dummy user to parse and get its modes + IrcUser tmp_user{nick, this->prefix_to_mode}; + // Does this concern ourself + if (channel->get_self() && channel->find_user(tmp_user.nick) == channel->get_self()) { - this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); + // We now know our own modes, that’s all. + channel->get_self()->modes = tmp_user.modes; } else - { - // we now know the modes of self, so copy the modes into self - channel->get_self()->modes = user->modes; + { // Otherwise this is a new user + const IrcUser *user = channel->add_user(nick, this->prefix_to_mode); + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } } } @@ -664,13 +667,11 @@ void IrcClient::on_channel_join(const IrcMessage& message) else channel = this->get_channel(chan_name); const std::string nick = message.prefix; + IrcUser* user = channel->add_user(nick, this->prefix_to_mode); if (channel->joined == false) - channel->set_self(nick); + channel->set_self(user); else - { - const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); - this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); - } + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } void IrcClient::on_channel_message(const IrcMessage& message) @@ -929,15 +930,14 @@ void IrcClient::on_part(const IrcMessage& message) if (user) { std::string nick = user->nick; + bool self = channel->get_self() && channel->get_self()->nick == nick; channel->remove_user(user); Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - bool self = channel->get_self()->nick == nick; if (self) { - channel->joined = false; this->channels.erase(utils::tolower(chan_name)); // channel pointer is now invalid channel = nullptr; -- cgit v1.2.3 From 0ee533256d13c3a19c45db376fa872ebe619bf21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 May 2017 21:54:12 +0200 Subject: Handle messages 367 and 368 to display the banlist in the MUC fix #3234 --- src/irc/irc_client.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/irc/irc_client.hpp | 2 ++ 2 files changed, 41 insertions(+) (limited to 'src') diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0e3926a..1d74098 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -62,6 +62,8 @@ static const std::unordered_mapbridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } +void IrcClient::on_banlist(const IrcMessage& message) +{ + const std::string chan_name = utils::tolower(message.arguments[1]); + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined) + { + Iid iid; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.type = Iid::Type::Channel; + std::string body{message.arguments[2] + " banned"}; + if (message.arguments.size() >= 4) + { + IrcUser by(message.arguments[3], this->prefix_to_mode); + body += " by " + by.nick; + } + if (message.arguments.size() >= 5) + body += " on " + message.arguments[4]; + + this->bridge.send_message(iid, "", body, true); + } +} + +void IrcClient::on_banlist_end(const IrcMessage& message) +{ + const std::string chan_name = utils::tolower(message.arguments[1]); + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined) + { + Iid iid; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.type = Iid::Type::Channel; + this->bridge.send_message(iid, "", message.arguments[2], true); + } +} + void IrcClient::on_own_host_received(const IrcMessage& message) { this->own_host = message.arguments[1]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7593029..aec6cd9 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -222,6 +222,8 @@ public: * received etc), send the self presence and topic to the XMPP user. */ void on_channel_completely_joined(const IrcMessage& message); + void on_banlist(const IrcMessage& message); + void on_banlist_end(const IrcMessage& message); /** * Save our own host, as reported by the server */ -- cgit v1.2.3 From cf1c8f188c64ffe6cce941027190ef3cd90abf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 21 May 2017 12:23:10 +0200 Subject: Remove a few warnings occuring in some build config --- src/bridge/bridge.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f2d782c..4a41b50 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -25,6 +25,8 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); return options.encodingIn.value(); #else + (void)bridge; + (void)iid; return {"ISO-8859-1"}; #endif } @@ -1000,6 +1002,10 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(), this->user_jid + "/" + resource, seconds); } +#else + (void)hostname; + (void)chan_name; + (void)resource; #endif } -- cgit v1.2.3 From 19ed2e7f5f182570c894a0a89d154f973fb01906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 May 2017 18:09:00 +0200 Subject: Fix the datetime parsing to handle optional fractions of seconds fix #3266 --- src/utils/time.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/utils/time.cpp b/src/utils/time.cpp index 88b3de3..bc2c18d 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -26,8 +26,8 @@ std::time_t parse_datetime(const std::string& stamp) std::istringstream ss(stamp); ss.imbue(std::locale("C")); - std::string timezone; - ss >> std::get_time(&t, format) >> timezone; + std::string remainings; + ss >> std::get_time(&t, format) >> remainings; if (ss.fail()) return -1; #else @@ -36,12 +36,22 @@ std::time_t parse_datetime(const std::string& stamp) if (!strptime(stamp.data(), format, &t)) { return -1; } - const std::string timezone(stamp.data() + stamp_size_without_tz); + const std::string remainings(stamp.data() + stamp_size_without_tz); #endif - if (timezone.empty()) + if (remainings.empty()) return -1; + std::string timezone; + // Skip optional fractions of seconds + if (remainings[0] == '.') + { + const auto pos = remainings.find_first_not_of(".0123456789"); + timezone = remainings.substr(pos); + } + else + timezone = std::move(remainings); + if (timezone.compare(0, 1, "Z") != 0) { std::stringstream tz_ss; -- cgit v1.2.3