~singpolyma/biboumi

507d0c2cbe3c41e3d8e6d38862fe418cb551adf3 — louiz’ 8 years ago 66609cf
Forward everything to all concerned XMPP resources
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +100 -28
@@ 156,9 156,15 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname)
    }
}

bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password)
{
  IrcClient* irc = this->make_irc_client(iid.get_server(), nickname);
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();
  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);
  if (!res_in_chan)
    this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource);
  if (iid.get_local().empty())
    { // Join the dummy channel
      if (irc->is_welcomed())


@@ 185,6 191,8 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const
    {
      irc->send_join_command(iid.get_local(), password);
      return true;
    } else if (!res_in_chan) {
      this->generate_channel_join_for_resource(iid, resource);
    }
  return false;
}


@@ 193,11 201,12 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
{
  if (iid.get_server().empty())
    {
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
                                    "cancel", "remote-server-not-found",
                                    std::to_string(iid) + " is not a valid channel name. "
                                    "A correct room jid is of the form: #<chan>%<server>",
                                    false);
      for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
        this->xmpp.send_stanza_error("message", this->user_jid + "/" + resource, std::to_string(iid), "",
                                     "cancel", "remote-server-not-found",
                                     std::to_string(iid) + " is not a valid channel name. "
                                         "A correct room jid is of the form: #<chan>%<server>",
                                     false);
      return;
    }
  IrcClient* irc = this->get_irc_client(iid.get_server());


@@ 226,8 235,9 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
        irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
      else
        irc->send_channel_message(iid.get_local(), line);
      this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
                                   this->make_xmpp_body(line), this->user_jid);
      for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
        this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
                                    this->make_xmpp_body(line), this->user_jid + "/" + resource);
    }
}



@@ 554,8 564,13 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
{
  const auto encoding = in_encoding_for(*this, iid);
  if (muc)
    this->xmpp.send_muc_message(std::to_string(iid), nick,
                                 this->make_xmpp_body(body, encoding), this->user_jid);
    {
      for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
        {
          this->xmpp.send_muc_message(std::to_string(iid), nick,
                                      this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource);
        }
    }
  else
    {
      std::string target = std::to_string(iid);


@@ 566,8 581,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
          target = it->second;
          fulljid = true;
        }
      this->xmpp.send_message(target, this->make_xmpp_body(body, encoding),
                               this->user_jid, "chat", fulljid);
      for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
        {
          this->xmpp.send_message(target, this->make_xmpp_body(body, encoding),
                                  this->user_jid + "/" + resource, "chat", fulljid);
        }
    }
}



@@ 580,7 598,8 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick,

void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self)
{
  this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self);
  for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
    this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self);
  IrcClient* irc = this->find_irc_client(iid.get_server());
  if (irc && irc->number_of_joined_channels() == 0)
    irc->send_quit_command("");


@@ 596,8 615,9 @@ void Bridge::send_nick_change(Iid&& iid,
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

  this->xmpp.send_nick_change(std::to_string(iid),
                               old_nick, new_nick, affiliation, role, this->user_jid, self);
  for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
    this->xmpp.send_nick_change(std::to_string(iid),
                                old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self);
}

void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg)


@@ 614,7 634,10 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho
    body = msg;

  const auto encoding = in_encoding_for(*this, {from});
  this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid, "chat");
  for (const auto& resource: this->resources_in_server[from])
    {
        this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat");
    }
}

void Bridge::send_user_join(const std::string& hostname,


@@ 627,15 650,21 @@ 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 + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
                             affiliation, role, this->user_jid, self);
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    {
      this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
                                affiliation, role, this->user_jid + "/" + resource, self);
    }
}

void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who)
{
  const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname});
  this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server(
      "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid, who);
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    {
      this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server(
          "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);
    }
}

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


@@ 653,12 682,14 @@ size_t Bridge::active_clients() const

void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author)
{
  this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid);
  for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
    this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource);
}

void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
{
  this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", "");
  for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
    this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", "");
}

void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)


@@ 667,7 698,8 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar
  std::string affiliation;

  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
  this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid);
  for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}])
    this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource);
}

void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)


@@ 722,7 754,7 @@ std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_cli
  return this->irc_clients;
}

void Bridge::add_resource_to_chan(const std::string& channel, const std::string& resource)
void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
{
  auto it = this->resources_in_chan.find(channel);
  if (it == this->resources_in_chan.end())


@@ 731,7 763,7 @@ void Bridge::add_resource_to_chan(const std::string& channel, const std::string&
    it->second.insert(resource);
}

void Bridge::remove_resource_from_chan(const std::string& channel, const std::string& resource)
void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource)
{
  auto it = this->resources_in_chan.find(channel);
  if (it != this->resources_in_chan.end())


@@ 742,7 774,7 @@ void Bridge::remove_resource_from_chan(const std::string& channel, const std::st
    }
}

bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& resource) const
bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const
{
  auto it = this->resources_in_chan.find(channel);
  if (it != this->resources_in_chan.end())


@@ 750,3 782,43 @@ bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& 
      return true;
  return false;
}

void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
{
  auto it = this->resources_in_server.find(irc_hostname);
  if (it == this->resources_in_server.end())
    this->resources_in_server[irc_hostname] = {resource};
  else
    it->second.insert(resource);
}

void Bridge::remove_resource_from_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
{
  auto it = this->resources_in_server.find(irc_hostname);
  if (it != this->resources_in_server.end())
    {
      it->second.erase(resource);
      if (it->second.empty())
        this->resources_in_server.erase(it);
    }
}

bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) const
{
  auto it = this->resources_in_server.find(irc_hostname);
  if (it != this->resources_in_server.end())
    if (it->second.count(resource) == 1)
      return true;
  return false;
}

void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* channel = irc->get_channel(iid.get_local());
  // Send the occupant list
  for (const auto& user: channel->get_users())
    {

    }
}

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +29 -8
@@ 65,7 65,7 @@ public:
   * Try to join an irc_channel, does nothing and return true if the channel
   * was already joined.
   */
  bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password);
  bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource);

  void send_channel_message(const Iid& iid, const std::string& body);
  void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");


@@ 195,11 195,9 @@ public:
  std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();

  /**
   * Manage which resource is in which channel
   * Manage which resource is connected to which IRC server
   */
  void add_resource_to_chan(const std::string& channel, const std::string& resource);
  void remove_resource_from_chan(const std::string& channel, const std::string& resource);
  bool is_resource_in_chan(const std::string& channel, const std::string& resource) const;


private:
  /**


@@ 218,7 216,7 @@ private:
   */
  IrcClient* find_irc_client(const std::string& hostname);
  /**
   * The JID of the user associated with this bridge. Messages from/to this
   * The bare JID of the user associated with this bridge. Messages from/to this
   * JID are only managed by this bridge.
   */
  const std::string user_jid;


@@ 251,10 249,33 @@ private:
   * response iq.
   */
  std::vector<irc_responder_callback_t> waiting_irc;

  /**
   * Resources to IRC channel/server mapping:
   */
  using Resource = std::string;
  using ChannelName = std::string;
  using IrcHostname = std::string;
  using ChannelKey = std::tuple<ChannelName, IrcHostname>;
  std::map<ChannelKey, std::set<Resource>> resources_in_chan;
  std::map<IrcHostname, std::set<Resource>> resources_in_server;
  /**
   * 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 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);
  bool is_resource_in_server(const IrcHostname& irc_hostname, const std::string& resource) const;

  /**
   * Keep track of which resource is in which channel.
   * Generate all the stanzas to be sent to this resource, simulating a join on this channel.
   * This means sending the whole user list, the topic, etc
   * TODO: send message history
   */
  std::map<std::string, std::set<std::string>> resources_in_chan;
  void generate_channel_join_for_resource(const Iid& iid, const std::string& resource);
};

struct IRCNotConnected: public std::exception

M src/irc/irc_channel.hpp => src/irc/irc_channel.hpp +2 -0
@@ 31,6 31,8 @@ public:
  IrcUser* find_user(const std::string& name) const;
  void remove_user(const IrcUser* user);
  void remove_all_users();
  const std::vector<std::unique_ptr<IrcUser>>& get_users() const
  { return this->users; }

protected:
  std::unique_ptr<IrcUser> self;

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +1 -1
@@ 701,7 701,7 @@ void IrcClient::empty_motd(const IrcMessage&)

void IrcClient::on_empty_topic(const IrcMessage& message)
{
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 1]);
  const std::string chan_name = utils::tolower(message.arguments[1]);
  log_debug("empty topic for " << chan_name);
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel)

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +10 -9
@@ 105,26 105,27 @@ void BiboumiComponent::clean()

void BiboumiComponent::handle_presence(const Stanza& stanza)
{
  std::string from = stanza.get_tag("from");
  std::string from_str = stanza.get_tag("from");
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

  // Check for mandatory tags
  if (from.empty())
  if (from_str.empty())
    {
      log_warning("Received an invalid presence stanza: tag 'from' is missing.");
      return;
    }
  if (to_str.empty())
    {
      this->send_stanza_error("presence", from, this->served_hostname, id,
      this->send_stanza_error("presence", from_str, this->served_hostname, id,
                              "modify", "bad-request", "Missing 'to' tag");
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
  Bridge* bridge = this->get_user_bridge(from_str);
  Jid to(to_str);
  Jid from(from_str);
  Iid iid(to.local);

  // An error stanza is sent whenever we exit this function without


@@ 136,7 137,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
      this->send_stanza_error("presence", from, to_str, id,
      this->send_stanza_error("presence", from_str, to_str, id,
                              error_type, error_name, "");
        });



@@ 151,8 152,8 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
            bridge->send_irc_nick_change(iid, to.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() : "");
          bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
                                   from.resource);
        }
      else if (type == "unavailable")
        {


@@ 164,12 165,12 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
    {
      // An user wants to join an invalid IRC channel, return a presence error to him
      if (type.empty())
        this->send_invalid_room_error(to.local, to.resource, from);
        this->send_invalid_room_error(to.local, to.resource, from_str);
    }
  }
  catch (const IRCNotConnected& ex)
    {
      this->send_stanza_error("presence", from, to_str, id,
      this->send_stanza_error("presence", from_str, to_str, id,
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);