~singpolyma/biboumi

7376831bc8f6dbec8eaf4f4c0a6bba819a0a1e59 — louiz’ 7 years ago 50d7590
Add get-irc-connection-info adhoc command

fix #3171
M doc/biboumi.1.rst => doc/biboumi.1.rst +7 -2
@@ 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

M louloulibs/network/tcp_socket_handler.cpp => louloulibs/network/tcp_socket_handler.cpp +12 -0
@@ 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;

M louloulibs/network/tcp_socket_handler.hpp => louloulibs/network/tcp_socket_handler.hpp +3 -0
@@ 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:
  /**

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +4 -0
@@ 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

M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +66 -1
@@ 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();
}

M src/xmpp/biboumi_adhoc_commands.hpp => src/xmpp/biboumi_adhoc_commands.hpp +2 -0
@@ 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);

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +6 -0
@@ 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);

M src/xmpp/biboumi_component.hpp => src/xmpp/biboumi_component.hpp +1 -1
@@ 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

M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +40 -2
@@ 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'),
    )