~singpolyma/biboumi

e840704b58a984351971e8034e74f5e9fdfaf114 — Florent Le Coz 9 years ago baf03a7
Convert received modes into roles and affiliations
M doc/biboumi.md => doc/biboumi.md +8 -4
@@ 184,17 184,21 @@ notified of this XMPP event as well. For example if a mode “+o toto” is
received, then toto’s role will be changed to moderator.  The mapping
between IRC modes and XMPP features is as follow:

`+a`

  Sets the participant’s role to `moderator` and its affiliation to `owner`.

`+o`

  Sets the participant’s role to `moderator`.
  Sets the participant’s role to `moderator` and its affiliation to  `admin`.

`+a`
`+h`

  Sets the participant’s role to `admin`.
  Sets the participant’s role to `moderator` and its affiliation to  `member`.

`+v`

  Sets the participant’s affiliation to `member`.
  Sets the participant’s role to `participant` and its affiliation to `member`.

SECURITY
--------

M src/bridge/bridge.cpp => src/bridge/bridge.cpp +32 -0
@@ 225,3 225,35 @@ void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nic
{
  this->xmpp->send_nickname_conflict_error(iid.chan + "%" + iid.server, nickname, this->user_jid);
}

void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
{
  std::string role;
  std::string affiliation;
  if (mode == 0)
    {
      role = "participant";
      affiliation = "none";
    }
  else if (mode == 'a')
    {
      role = "moderator";
      affiliation = "owner";
    }
  else if (mode == 'o')
    {
      role = "moderator";
      affiliation = "admin";
    }
  else if (mode == 'h')
    {
      role = "moderator";
      affiliation = "member";
    }
  else if (mode == 'v')
    {
      role = "participant";
      affiliation = "member";
    }
  this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid);
}

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +4 -0
@@ 87,6 87,10 @@ public:
  void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self);
  void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author);
  void send_nickname_conflict_error(const Iid& iid, const std::string& nickname);
  /**
   * Send a role/affiliation change, matching the change of mode for that user
   */
  void send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode);

  /**
   * Misc

M src/irc/irc_channel.cpp => src/irc/irc_channel.cpp +1 -1
@@ 24,7 24,7 @@ IrcUser* IrcChannel::get_self() const
  return this->self.get();
}

IrcUser* IrcChannel::find_user(const std::string& name)
IrcUser* IrcChannel::find_user(const std::string& name) const
{
  IrcUser user(name);
  for (const auto& u: this->users)

M src/irc/irc_channel.hpp => src/irc/irc_channel.hpp +1 -1
@@ 22,7 22,7 @@ public:
  IrcUser* get_self() const;
  IrcUser* add_user(const std::string& name,
                    const std::map<char, char> prefix_to_mode);
  IrcUser* find_user(const std::string& name);
  IrcUser* find_user(const std::string& name) const;
  void remove_user(const IrcUser* user);

private:

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +74 -1
@@ 218,7 218,10 @@ void IrcClient::on_isupport_message(const IrcMessage& message)
          j++;
        j++;
        while (j < token.size() && token[i] != ')')
          this->prefix_to_mode[token[j++]] = token[i++];
          {
            this->sorted_user_modes.push_back(token[i]);
            this->prefix_to_mode[token[j++]] = token[i++];
          }
      }
  }
}


@@ 508,6 511,76 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
  this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan +
                                      " [" + mode_arguments + "] by " + user.nick,
                             true);
  const IrcChannel* channel = this->get_channel(iid.chan);
  if (!channel)
    return;

  // parse the received modes, we need to handle things like "+m-oo coucou toutou"
  const std::string modes = message.arguments[1];
  // a list of modified IrcUsers. When we applied all modes, we check the
  // modes that now applies to each of them, and send a notification for
  // each one. This is to disallow sending two notifications or more when a
  // single MODE command changes two or more modes on the same participant
  std::set<const IrcUser*> modified_users;
  // If it is true, the modes are added, if it’s false they are
  // removed. When we encounter the '+' char, the value is changed to true,
  // and with '-' it is changed to false.
  bool add = true;
  bool use_arg;
  size_t arg_pos = 2;
  for (const char c: modes)
    {
      if (c == '+')
        add = true;
      else if (c == '-')
        add = false;
      else
        { // lookup the mode symbol in the 4 chanmodes lists, depending on
          // the list where it is found, it takes an argument or not
          size_t type;
          for (type = 0; type < 4; ++type)
            if (this->chanmodes[type].find(c) != std::string::npos)
              break;
          if (type == 4)        // if mode was not found
            {
              // That mode can also be of type B if it is present in the
              // prefix_to_mode map
              for (const std::pair<char, char>& pair: this->prefix_to_mode)
                if (pair.second == c)
                  {
                    type = 1;
                    break;
                  }
            }
          // modes of type A, B or C (but only with add == true)
          if (type == 0 || type == 1 ||
              (type == 2 && add == true))
            use_arg = true;
          else // modes of type C (but only with add == false), D, or unknown
            use_arg = false;
          if (use_arg == true && message.arguments.size() > arg_pos)
            {
              const std::string target = message.arguments[arg_pos++];
              IrcUser* user = channel->find_user(target);
              if (!user)
                {
                  log_warning("Trying to set mode for non-existing user '" << target
                              << "' in channel" << iid.chan);
                  return;
                }
              if (add)
                user->add_mode(c);
              else
                user->remove_mode(c);
              modified_users.insert(user);
            }
        }
    }
  for (const IrcUser* u: modified_users)
    {
      char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes);
      this->bridge->send_affiliation_role_change(iid, u->nick, most_significant_mode);
    }
}

void IrcClient::on_user_mode(const IrcMessage& message)

M src/irc/irc_client.hpp => src/irc/irc_client.hpp +6 -1
@@ 229,9 229,14 @@ private:
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14
   * The example given would be transformed into
   * modes_to_prefix = {{'a', '&'}, {'b', '*'}}
   * modes_to_prefix = {{'&', 'a'}, {'*', 'b'}}
   */
  std::map<char, char> prefix_to_mode;
  /**
   * Available user modes, sorted from most significant to least significant
   * (for example 'ahov' is a common order).
   */
  std::vector<char> sorted_user_modes;

  IrcClient(const IrcClient&) = delete;
  IrcClient(IrcClient&&) = delete;

M src/irc/irc_user.cpp => src/irc/irc_user.cpp +20 -0
@@ 25,3 25,23 @@ IrcUser::IrcUser(const std::string& name):
  IrcUser(name, {})
{
}

void IrcUser::add_mode(const char mode)
{
  this->modes.insert(mode);
}

void IrcUser::remove_mode(const char mode)
{
  this->modes.erase(mode);
}

char IrcUser::get_most_significant_mode(const std::vector<char>& modes) const
{
  for (const char mode: modes)
    {
      if (this->modes.find(mode) != this->modes.end())
        return mode;
    }
  return 0;
}

M src/irc/irc_user.hpp => src/irc/irc_user.hpp +4 -0
@@ 1,6 1,7 @@
#ifndef IRC_USER_INCLUDED
# define IRC_USER_INCLUDED

#include <vector>
#include <string>
#include <map>
#include <set>


@@ 14,6 15,9 @@ public:
  explicit IrcUser(const std::string& name,
                   const std::map<char, char>& prefix_to_mode);
  explicit IrcUser(const std::string& name);
  void add_mode(const char mode);
  void remove_mode(const char mode);
  char get_most_significant_mode(const std::vector<char>& sorted_user_modes) const;
  std::string nick;
  std::string host;
  std::set<char> modes;

M src/xmpp/xmpp_component.cpp => src/xmpp/xmpp_component.cpp +22 -0
@@ 484,3 484,25 @@ void XmppComponent::send_nickname_conflict_error(const std::string& muc_name,
  presence.close();
  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;
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["affiliation"] = affiliation;
  item["role"] = role;
  item.close();
  x.add_child(std::move(item));
  x.close();
  presence.add_child(std::move(x));
  presence.close();
  this->send_stanza(presence);
}

M src/xmpp/xmpp_component.hpp => src/xmpp/xmpp_component.hpp +9 -0
@@ 113,6 113,15 @@ public:
                                    const std::string& nickname,
                                    const std::string& jid_to);
  /**
   * 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);
  /**
   * Handle the various stanza types
   */
  void handle_handshake(const Stanza& stanza);