~singpolyma/biboumi

2df0ebf2dfed1dcbf80c92bff8361e2a04581bec — Florent Le Coz 7 years ago 1c43c3a
Add support for a fixed_irc_server configuration

This option lets the administrator choose a specific IRC server, and only
that server can be used with this biboumi instance.

In this mode, JIDs to use are changed like this:

- #chan%irc.example.com@biboumi.example.com      -> #chan@biboumi.example.com
- user!irc.example.com@biboumi.example.com       -> user!@biboumi.example.com
- #chan%irc.example.com@biboumi.example.com/Nick -> #chan@biboumi.example.com/Nick
- %irc.example.com@biboumi.example.com           -> no equivalent
- irc.example.com@biboumi.example.com            -> no equivalent
M doc/biboumi.1.md => doc/biboumi.1.md +14 -0
@@ 59,6 59,20 @@ The configuration file uses a simple format of the form
  privileges), for example some administration ad-hoc commands will only be
  available to that JID.

`fixed_irc_server`

  If this option contains the hostname of an IRC server (for example
  irc.example.org), then biboumi will enforce the connexion to that IRC
  server only.  This means that a JID like "#chan@irc.biboumi.org" must be
  used instead of "#chan%irc.example.org@irc.biboumi.org".  In that mode,
  the virtual channel (see *Connect to an IRC server*) is not available and
  you still need to use the ! separator to send message to an IRC user (for
  example "foo!@biboumi.example.com" to send a message to foo), although the
  in-room JID still work as expected ("#channel@biboumi.example.com/Nick").
  This option can for example be used by an administrator that just wants to
  let their users join their own IRC server using an XMPP client, but
  without letting them join any other IRC servers on the internet.

`log_file`

  A filename into which logs are written.  If none is provided, the logs are

M src/bridge/bridge.cpp => src/bridge/bridge.cpp +5 -4
@@ 4,6 4,7 @@
#include <xmpp/xmpp_stanza.hpp>
#include <irc/irc_message.hpp>
#include <network/poller.hpp>
#include <utils/empty_if_fixed_server.hpp>
#include <utils/encoding.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>


@@ 542,13 543,13 @@ void Bridge::send_user_join(const std::string& hostname,
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

  this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host,
  this->xmpp->send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
                             affiliation, role, this->user_jid, self);
}

void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic)
{
  this->xmpp->send_topic(chan_name + "%" + hostname, this->make_xmpp_body(topic), this->user_jid);
  this->xmpp->send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic), this->user_jid);
}

std::string Bridge::get_own_nick(const Iid& iid)


@@ 585,7 586,7 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar

void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
{
  this->xmpp->send_iq_version_request(nick + "!" + hostname, this->user_jid);
  this->xmpp->send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid);
}

void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,


@@ 594,7 595,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& 
  // 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
  // (revstr again)
  this->xmpp->send_ping_request(nick + "!" + hostname, this->user_jid, utils::revstr(id));
  this->xmpp->send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id));
}

void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)

M src/irc/iid.cpp => src/irc/iid.cpp +43 -1
@@ 1,4 1,5 @@
#include <utils/tolower.hpp>
#include <config/config.hpp>

#include <irc/iid.hpp>



@@ 6,6 7,16 @@ Iid::Iid(const std::string& iid):
  is_channel(false),
  is_user(false)
{
  const std::string fixed_irc_server = Config::get("fixed_irc_server", "");
  if (fixed_irc_server.empty())
    this->init(iid);
  else
    this->init_with_fixed_server(iid, fixed_irc_server);
}


void Iid::init(const std::string& iid)
{
  const std::string::size_type sep = iid.find_first_of("%!");
  if (sep != std::string::npos)
    {


@@ 20,6 31,29 @@ Iid::Iid(const std::string& iid):
    this->set_server(iid);
}

void Iid::init_with_fixed_server(const std::string& iid, const std::string& hostname)
{
  this->set_server(hostname);

  const std::string::size_type sep = iid.find_first_of("%!");

  // Without any separator, we consider that it's a channel
  if (sep == std::string::npos)
    {
      this->is_channel = true;
      this->set_local(iid);
    }
  else // A separator can be present to differenciate a channel from a user,
       // but the part behind it (the hostname) is ignored
    {
      this->set_local(iid.substr(0, sep));
      if (iid[sep] == '%')
        this->is_channel = true;
      else
        this->is_user = true;
    }
}

Iid::Iid(const Iid& other):
  is_channel(other.is_channel),
  is_user(other.is_user),


@@ 66,6 100,14 @@ std::string Iid::get_sep() const
namespace std {
  const std::string to_string(const Iid& iid)
  {
    return iid.get_local() + iid.get_sep() + iid.get_server();
    if (Config::get("fixed_irc_server", "").empty())
      return iid.get_local() + iid.get_sep() + iid.get_server();
    else
      {
        if (iid.get_sep() == "!")
          return iid.get_local() + iid.get_sep();
        else
          return iid.get_local();
      }
  }
}

M src/irc/iid.hpp => src/irc/iid.hpp +4 -0
@@ 57,6 57,10 @@ public:
  std::string get_sep() const;

private:

  void init(const std::string& iid);
  void init_with_fixed_server(const std::string& iid, const std::string& hostname);

  std::string local;
  std::string server;


M src/test.cpp => src/test.cpp +94 -32
@@ 303,38 303,100 @@ int main()
  /**
   * IID parsing
   */
  std::cout << color << "Testing IID parsing…" << reset << std::endl;
  Iid iid1("foo!irc.example.org");
  std::cout << std::to_string(iid1) << std::endl;
  assert(std::to_string(iid1) == "foo!irc.example.org");
  assert(iid1.get_local() == "foo");
  assert(iid1.get_server() == "irc.example.org");
  assert(!iid1.is_channel);
  assert(iid1.is_user);

  Iid iid2("#test%irc.example.org");
  std::cout << std::to_string(iid2) << std::endl;
  assert(std::to_string(iid2) == "#test%irc.example.org");
  assert(iid2.get_local() == "#test");
  assert(iid2.get_server() == "irc.example.org");
  assert(iid2.is_channel);
  assert(!iid2.is_user);

  Iid iid3("%irc.example.org");
  std::cout << std::to_string(iid3) << std::endl;
  assert(std::to_string(iid3) == "%irc.example.org");
  assert(iid3.get_local() == "");
  assert(iid3.get_server() == "irc.example.org");
  assert(iid3.is_channel);
  assert(!iid3.is_user);

  Iid iid4("irc.example.org");
  std::cout << std::to_string(iid4) << std::endl;
  assert(std::to_string(iid4) == "irc.example.org");
  assert(iid4.get_local() == "");
  assert(iid4.get_server() == "irc.example.org");
  assert(!iid4.is_channel);
  assert(!iid4.is_user);
  {
    std::cout << color << "Testing IID parsing…" << reset << std::endl;
    Iid iid1("foo!irc.example.org");
    std::cout << std::to_string(iid1) << std::endl;
    assert(std::to_string(iid1) == "foo!irc.example.org");
    assert(iid1.get_local() == "foo");
    assert(iid1.get_server() == "irc.example.org");
    assert(!iid1.is_channel);
    assert(iid1.is_user);

    Iid iid2("#test%irc.example.org");
    std::cout << std::to_string(iid2) << std::endl;
    assert(std::to_string(iid2) == "#test%irc.example.org");
    assert(iid2.get_local() == "#test");
    assert(iid2.get_server() == "irc.example.org");
    assert(iid2.is_channel);
    assert(!iid2.is_user);

    Iid iid3("%irc.example.org");
    std::cout << std::to_string(iid3) << std::endl;
    assert(std::to_string(iid3) == "%irc.example.org");
    assert(iid3.get_local() == "");
    assert(iid3.get_server() == "irc.example.org");
    assert(iid3.is_channel);
    assert(!iid3.is_user);

    Iid iid4("irc.example.org");
    std::cout << std::to_string(iid4) << std::endl;
    assert(std::to_string(iid4) == "irc.example.org");
    assert(iid4.get_local() == "");
    assert(iid4.get_server() == "irc.example.org");
    assert(!iid4.is_channel);
    assert(!iid4.is_user);

    Iid iid5("nick!");
    std::cout << std::to_string(iid5) << std::endl;
    assert(std::to_string(iid5) == "nick!");
    assert(iid5.get_local() == "nick");
    assert(iid5.get_server() == "");
    assert(!iid5.is_channel);
    assert(iid5.is_user);

    Iid iid6("##channel%");
    std::cout << std::to_string(iid6) << std::endl;
    assert(std::to_string(iid6) == "##channel%");
    assert(iid6.get_local() == "##channel");
    assert(iid6.get_server() == "");
    assert(iid6.is_channel);
    assert(!iid6.is_user);
  }

  {
    std::cout << color << "Testing IID parsing with a fixed server configured…" << reset << std::endl;
    // Now do the same tests, but with a configured fixed_irc_server
    Config::set("fixed_irc_server", "fixed.example.com", false);

    Iid iid1("foo!irc.example.org");
    std::cout << std::to_string(iid1) << std::endl;
    assert(std::to_string(iid1) == "foo!");
    assert(iid1.get_local() == "foo");
    assert(iid1.get_server() == "fixed.example.com");
    assert(!iid1.is_channel);
    assert(iid1.is_user);

    Iid iid2("#test%irc.example.org");
    std::cout << std::to_string(iid2) << std::endl;
    assert(std::to_string(iid2) == "#test");
    assert(iid2.get_local() == "#test");
    assert(iid2.get_server() == "fixed.example.com");
    assert(iid2.is_channel);
    assert(!iid2.is_user);

    // Note that it is impossible to adress the XMPP server directly, or to
    // use the virtual channel, in that mode

    // Iid iid3("%irc.example.org");
    // Iid iid4("irc.example.org");

    Iid iid5("nick!");
    std::cout << std::to_string(iid5) << std::endl;
    assert(std::to_string(iid5) == "nick!");
    assert(iid5.get_local() == "nick");
    assert(iid5.get_server() == "fixed.example.com");
    assert(!iid5.is_channel);
    assert(iid5.is_user);

    Iid iid6("##channel%");
    std::cout << std::to_string(iid6) << std::endl;
    assert(std::to_string(iid6) == "##channel");
    assert(iid6.get_local() == "##channel");
    assert(iid6.get_server() == "fixed.example.com");
    assert(iid6.is_channel);
    assert(!iid6.is_user);
  }

  return 0;
}

A src/utils/empty_if_fixed_server.cpp => src/utils/empty_if_fixed_server.cpp +8 -0
@@ 0,0 1,8 @@
// #include <utils/empty_if_fixed_server.hpp>

// #include <config/config.hpp>

// namespace utils
// {
//   inline std::string empty_if_fixed_server(std::string&& str)
// }

A src/utils/empty_if_fixed_server.hpp => src/utils/empty_if_fixed_server.hpp +26 -0
@@ 0,0 1,26 @@
#ifndef EMPTY_IF_FIXED_SERVER_HPP_INCLUDED
#define EMPTY_IF_FIXED_SERVER_HPP_INCLUDED

#include <string>

#include <config/config.hpp>

namespace utils
{
  inline std::string empty_if_fixed_server(std::string&& str)
  {
    if (!Config::get("fixed_irc_server", "").empty())
      return {};
    return str;
  }

  inline std::string empty_if_fixed_server(const std::string& str)
  {
    if (!Config::get("fixed_irc_server", "").empty())
      return {};
    return str;
  }

}

#endif /* EMPTY_IF_FIXED_SERVER_HPP_INCLUDED */