diff options
-rw-r--r-- | doc/biboumi.1.rst | 9 | ||||
-rw-r--r-- | louloulibs/network/tcp_socket_handler.cpp | 12 | ||||
-rw-r--r-- | louloulibs/network/tcp_socket_handler.hpp | 3 | ||||
-rw-r--r-- | src/bridge/bridge.hpp | 4 | ||||
-rw-r--r-- | src/xmpp/biboumi_adhoc_commands.cpp | 67 | ||||
-rw-r--r-- | src/xmpp/biboumi_adhoc_commands.hpp | 2 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.cpp | 6 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.hpp | 2 | ||||
-rw-r--r-- | tests/end_to_end/__main__.py | 42 |
9 files changed, 141 insertions, 6 deletions
diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 8f5eb9b..49c0fe4 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -488,13 +488,18 @@ On the gateway itself (e.g on the JID biboumi.example.com): On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Configure: Lets each user configure some options that applies to the +- configure: Lets each user configure some options that applies to the concerned IRC server. +- get-irc-connection-info: Returns some information about the IRC server, + for the executing user. It lets the user know if they are connected to + this server, from what port, with or without TLS, and it gives the list + of joined IRC channel, with a detailed list of which resource is in which + channel. On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Configure: Lets each user configure some options that applies to the +- configure: Lets each user configure some options that applies to the concerned IRC channel. Some of these options, if not configured for a specific channel, defaults to the value configured at the IRC server level. For example the encoding can be specified for both the channel diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 9decee1..1dddde5 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -179,6 +179,8 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po if (this->use_tls) this->start_tls(); #endif + this->connection_date = std::chrono::system_clock::now(); + this->on_connected(); return ; } @@ -397,6 +399,16 @@ bool TCPSocketHandler::is_connecting() const return this->connecting || this->resolver.is_resolving(); } +bool TCPSocketHandler::is_using_tls() const +{ + return this->use_tls; +} + +std::string TCPSocketHandler::get_port() const +{ + return this->port; +} + void* TCPSocketHandler::get_receive_buffer(const size_t) const { return nullptr; diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 7bbe4d4..6c4235e 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -106,6 +106,9 @@ public: #endif bool is_connected() const override final; bool is_connecting() const; + bool is_using_tls() const; + std::string get_port() const; + std::chrono::system_clock::time_point connection_date; private: /** diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b2432f0..18ebfeb 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -248,10 +248,12 @@ private: * a IRCServerNotConnected error in that case. */ IrcClient* get_irc_client(const std::string& hostname); +public: /** * Idem, but returns nullptr if the server does not exist. */ IrcClient* find_irc_client(const std::string& hostname) const; +private: /** * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. @@ -293,7 +295,9 @@ private: using ChannelName = std::string; using IrcHostname = std::string; using ChannelKey = std::tuple<ChannelName, IrcHostname>; +public: std::map<ChannelKey, std::set<Resource>> resources_in_chan; +private: std::map<IrcHostname, std::set<Resource>> resources_in_server; /** * Manage which resource is in which channel diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index af7e473..87b8d96 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,10 +1,12 @@ #include <xmpp/biboumi_adhoc_commands.hpp> #include <xmpp/biboumi_component.hpp> +#include <utils/scopeguard.hpp> +#include <bridge/bridge.hpp> #include <config/config.hpp> #include <utils/string.hpp> #include <utils/split.hpp> #include <xmpp/jid.hpp> -#include <algorithm> +#include <iomanip> #include <biboumi.h> @@ -720,3 +722,66 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& note.set_inner(msg); command_node.add_child(std::move(note)); } + +void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node) +{ + BiboumiComponent& biboumi_component = static_cast<BiboumiComponent&>(component); + + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + std::string message{}; + + // As the function is exited, set the message in the response. + utils::ScopeGuard sg([&message, &command_node]() + { + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner(message); + command_node.add_child(std::move(note)); + }); + + Bridge* bridge = biboumi_component.get_user_bridge(owner.bare()); + if (!bridge) + { + message = "You are not connected to anything."; + return; + } + + std::string hostname; + if ((hostname = Config::get("fixed_irc_server", "")).empty()) + hostname = target.local; + + IrcClient* irc = bridge->find_irc_client(hostname); + if (!irc || !irc->is_connected()) + { + message = "You are not connected to the IRC server "s + hostname; + return; + } + + std::ostringstream ss; + ss << "Connected to IRC server " << irc->get_hostname() << " on port " << irc->get_port(); + if (irc->is_using_tls()) + ss << " (using TLS)"; + const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date); + ss << " since " << std::put_time(std::localtime(&now_c), "%F %T"); + ss << " (" << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago)."; + + for (const auto& it: bridge->resources_in_chan) + { + const auto& channel_key = it.first; + const auto& irc_hostname = std::get<1>(channel_key); + const auto& resources = it.second; + + if (irc_hostname == irc->get_hostname() && !resources.empty()) + { + const auto& channel_name = std::get<0>(channel_key); + ss << "\n" << channel_name << " from " << resources.size() << " resource" << (resources.size() > 1 ? "s": "") << ": "; + for (const auto& resource: resources) + ss << resource << " "; + } + } + + message = ss.str(); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 7be5509..b5fce61 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -22,3 +22,5 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); + +void GetIrcConnectionInfoStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f3405df..4398562 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -63,6 +63,12 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::st this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false}); this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); + AdhocCommand get_irc_connection_info{{&GetIrcConnectionInfoStep1}, "Returns various information about your connection to this IRC server.", false}; + if (!Config::get("fixed_irc_server", "").empty()) + this->adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + else + this->irc_server_adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index d5b87e9..999001f 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -101,13 +101,13 @@ public: const std::string& queryid); #endif -private: /** * Return the bridge associated with the bare JID. Create a new one * if none already exist. */ Bridge* get_user_bridge(const std::string& user_jid); +private: /** * A map of id -> callback. When we want to wait for an iq result, we add * the callback to this map, with the iq id as the key. When an iq result diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a7a5f69..239f044 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -631,7 +631,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[3]")), + "/iq/disco_items:query/disco_items:item[5]")), ], conf='fixed_server'), Scenario("list_admin_adhoc_fixed_server", [ @@ -647,7 +647,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{irc_host_one}@{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[1]")), + "/iq/disco_items:query/disco_items:item[2]")), ]), Scenario("list_adhoc_irc_fixed_server", [ @@ -1798,6 +1798,44 @@ if __name__ == '__main__': partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), ]), + Scenario("get_irc_connection_info", + [ + handshake_sequence(), + + partial(log_message, "Not connected yet"), + partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + partial(log_message, "Join one room"), + partial(send_stanza, + "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), + ]), + Scenario("get_irc_connection_info_fixed", + [ + handshake_sequence(), + + partial(log_message, "Not connected yet"), + partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + partial(log_message, "Join one room"), + partial(send_stanza, + "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), + ], conf='fixed_server'), ) |