bridge.cpp 48 KB
Newer Older
1
#include <bridge/bridge.hpp>
louiz’'s avatar
louiz’ committed
2
#include <utility>
3
#include <xmpp/biboumi_component.hpp>
4
#include <network/poller.hpp>
5
#include <utils/empty_if_fixed_server.hpp>
6
#include <utils/encoding.hpp>
7
#include <utils/tolower.hpp>
8
#include <utils/uuid.hpp>
louiz’'s avatar
louiz’ committed
9
#include <logger/logger.hpp>
10
#include <utils/revstr.hpp>
11
#include <utils/split.hpp>
12
#include <xmpp/jid.hpp>
13
#include <database/database.hpp>
14
#include "result_set_management.hpp"
louiz’'s avatar
louiz’ committed
15
#include <algorithm>
louiz’'s avatar
louiz’ committed
16

louiz’'s avatar
louiz’ committed
17 18
using namespace std::string_literals;

19 20
static const char* action_prefix = "\01ACTION ";

21 22 23 24 25

static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
{
#ifdef USE_DATABASE
  const auto jid = bridge.get_bare_jid();
26
  return Database::get_encoding_in(jid, iid.get_server(), iid.get_local());
27
#else
28 29
  (void)bridge;
  (void)iid;
30
  return {"ISO-8859-1"};
31
#endif
32 33
}

louiz’'s avatar
louiz’ committed
34
Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
35
 user_jid(std::move(user_jid)),
36 37 38
  xmpp(xmpp),
  poller(poller)
{
39 40
#ifdef USE_DATABASE
  const auto options = Database::get_global_options(this->user_jid);
41
  this->set_record_history(options.col<Database::RecordHistory>());
42
#endif
43 44
}

45
/**
46 47
 * Return the role and affiliation, corresponding to the given irc mode
 */
48 49
static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(const char mode)
{
50 51
  if (mode == 'a' || mode == 'q'){
    return std::make_tuple("moderator", "owner");}
52 53 54 55 56 57 58 59 60 61
  else if (mode == 'o')
    return std::make_tuple("moderator", "admin");
  else if (mode == 'h')
    return std::make_tuple("moderator", "member");
  else if (mode == 'v')
    return std::make_tuple("participant", "member");
  else
    return std::make_tuple("participant", "none");
}

62
void Bridge::shutdown(const std::string& exit_message)
louiz’'s avatar
louiz’ committed
63
{
louiz’'s avatar
louiz’ committed
64
  for (auto& pair: this->irc_clients)
louiz’'s avatar
louiz’ committed
65
  {
louiz’'s avatar
louiz’ committed
66
    pair.second->send_quit_command(exit_message);
louiz’'s avatar
louiz’ committed
67 68 69
  }
}

70 71 72 73 74 75 76 77 78 79 80 81 82 83
void Bridge::remove_resource(const std::string& resource,
                             const std::string& part_message)
{
  const auto resources_in_chan_copy = this->resources_in_chan;
  for (const auto& chan_pair: resources_in_chan_copy)
  {
    const ChannelKey& channel_key = chan_pair.first;
    const std::set<Resource>& resources = chan_pair.second;
    if (resources.count(resource))
      this->leave_irc_channel({std::get<0>(channel_key), std::get<1>(channel_key), {}},
                              part_message, resource);
  }
}

louiz’'s avatar
louiz’ committed
84 85 86 87 88 89
void Bridge::clean()
{
  auto it = this->irc_clients.begin();
  while (it != this->irc_clients.end())
  {
    IrcClient* client = it->second.get();
louiz’'s avatar
louiz’ committed
90 91
    if (!client->is_connected() && !client->is_connecting() &&
        !client->get_resolver().is_resolving())
louiz’'s avatar
louiz’ committed
92 93 94 95 96 97
      it = this->irc_clients.erase(it);
    else
      ++it;
  }
}

98 99 100 101 102
const std::string& Bridge::get_jid() const
{
  return this->user_jid;
}

louiz’'s avatar
louiz’ committed
103 104 105 106 107 108
std::string Bridge::get_bare_jid() const
{
  Jid jid(this->user_jid);
  return jid.local + "@" + jid.domain;
}

109
Xmpp::body Bridge::make_xmpp_body(const std::string& str, const std::string& encoding)
110 111 112 113 114
{
  std::string res;
  if (utils::is_valid_utf8(str.c_str()))
    res = str;
  else
115
    res = utils::convert_to_utf8(str, encoding.data());
116
  return irc_format_to_xhtmlim(res);
117 118
}

119
IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname)
120 121 122 123 124 125 126
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
127 128
      auto username = nickname;
      auto realname = nickname;
louiz’'s avatar
louiz’ committed
129
      Jid jid(this->user_jid);
130 131 132 133 134 135 136 137
      if (Config::get("realname_from_jid", "false") == "true")
        {
          username = jid.local;
          realname = this->get_bare_jid();
        }
      this->irc_clients.emplace(hostname,
                                std::make_shared<IrcClient>(this->poller, hostname,
                                                            nickname, username,
louiz’'s avatar
louiz’ committed
138
                                                            realname, jid.domain,
139
                                                            *this));
140 141 142 143 144
      std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
      return irc.get();
    }
}

louiz’'s avatar
louiz’ committed
145
IrcClient* Bridge::get_irc_client(const std::string& hostname)
146 147 148 149 150 151 152 153 154 155 156
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
      throw IRCNotConnected(hostname);
    }
}

157
IrcClient* Bridge::find_irc_client(const std::string& hostname) const
louiz’'s avatar
louiz’ committed
158 159 160 161 162 163 164 165 166 167 168
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
      return nullptr;
    }
}

169 170 171 172 173
bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
                              const std::string& password,
                              const std::string& resource,
                              HistoryLimit history_limit,
                              const bool force_join)
174
{
louiz’'s avatar
louiz’ committed
175
  const auto& hostname = iid.get_server();
176 177 178 179 180
#ifdef USE_DATABASE
  auto soptions = Database::get_irc_server_options(this->get_bare_jid(), hostname);
  if (!soptions.col<Database::Nick>().empty())
    nickname = soptions.col<Database::Nick>();
#endif
181
  IrcClient* irc = this->make_irc_client(hostname, nickname);
182
  irc->history_limit = history_limit;
183 184 185 186
  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);
louiz’'s avatar
louiz’ committed
187
  if (irc->is_channel_joined(iid.get_local()) == false)
louiz’'s avatar
louiz’ committed
188
    {
189
      irc->send_join_command(iid.get_local(), password);
louiz’'s avatar
louiz’ committed
190
      return true;
191 192
    } else if (!res_in_chan || force_join) {
      // See https://github.com/xsf/xeps/pull/499 for the force_join argument
193
      this->generate_channel_join_for_resource(iid, resource);
louiz’'s avatar
louiz’ committed
194 195
    }
  return false;
196 197
}

198
void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::string id)
louiz’'s avatar
louiz’ committed
199
{
200
  if (iid.get_server().empty())
louiz’'s avatar
louiz’ committed
201
    {
202
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
203 204 205 206 207
        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);
louiz’'s avatar
louiz’ committed
208 209
      return;
    }
louiz’'s avatar
louiz’ committed
210
  IrcClient* irc = this->get_irc_client(iid.get_server());
211

212 213 214 215 216 217 218 219 220 221 222
  // Because an IRC message cannot contain \n, we need to convert each line
  // of text into a separate IRC message. For conveniance, we also cut the
  // message into submessages on the XMPP side, this way the user of the
  // gateway sees what was actually sent over IRC.  For example if an user
  // sends “hello\n/me waves”, two messages will be generated: “hello” and
  // “/me waves”. Note that the “command” handling (messages starting with
  // /me, /mode, etc) is done for each message generated this way. In the
  // above example, the /me will be interpreted.
  std::vector<std::string> lines = utils::split(body, '\n', true);
  if (lines.empty())
    return ;
223
  bool first = true;
224
  for (const std::string& line: lines)
225
    {
226
      if (line.substr(0, 5) == "/mode")
227
        {
228
          std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
louiz’'s avatar
louiz’ committed
229
          irc->send_mode_command(iid.get_local(), args);
230 231 232 233
          continue;             // We do not want to send that back to the
                                // XMPP user, that’s not a textual message.
        }
      else if (line.substr(0, 4) == "/me ")
louiz’'s avatar
louiz’ committed
234
        irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
235
      else
louiz’'s avatar
louiz’ committed
236
        irc->send_channel_message(iid.get_local(), line);
237

238
      std::string uuid;
239
#ifdef USE_DATABASE
240
      const auto xmpp_body = this->make_xmpp_body(line);
241
      if (this->record_history)
242
        uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
243
                                    std::get<0>(xmpp_body), irc->get_own_nick());
244
#endif
245
      if (!first || id.empty())
246
        id = utils::gen_uuid();
247
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
248
        this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
249 250
                                    this->user_jid + "/" + resource, uuid, id);
      first = false;
251
    }
louiz’'s avatar
louiz’ committed
252 253
}

254 255
void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& from,
                                             const std::string& nick,
256
                                             const std::string& affiliation,
257 258
                                             const std::string& role,
                                             const std::string& id)
259 260 261 262 263 264 265
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
  if (!chan || !chan->joined)
    return;
  IrcUser* user = chan->find_user(nick);
  if (!user)
266 267 268 269 270
    {
      this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel",
                                   "item-not-found", "no such nick", false);
      return;
    }
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
  // For each affiliation or role, we have a “maximal” mode that we want to
  // set. We must remove any superior mode at the same time. For example if
  // the user already has +o mode, and we set its affiliation to member, we
  // remove the +o mode, and add +v.  For each “superior” mode (for example,
  // for +v, the superior modes are 'h', 'a', 'o' and 'q') we check if that
  // user has it, and if yes we remove that mode

  std::size_t nb = 1;               // the number of times the nick must be
                                    // repeated in the argument list
  std::string modes;                // The string of modes to
                                    // add/remove. For example "+v-aoh"
  std::vector<char> modes_to_remove; // List of modes to check for removal
  if (affiliation == "none")
    {
      modes = "";
      nb = 0;
      modes_to_remove = {'v', 'h', 'o', 'a', 'q'};
    }
  else if (affiliation == "member")
    {
      modes = "+v";
      modes_to_remove = {'h', 'o', 'a', 'q'};
    }
  else if (role == "moderator")
    {
      modes = "+h";
      modes_to_remove = {'o', 'a', 'q'};
    }
  else if (affiliation == "admin")
    {
      modes = "+o";
      modes_to_remove = {'a', 'q'};
    }
  else if (affiliation == "owner")
    {
      modes = "+a";
      modes_to_remove = {'q'};
    }
  else
    return;
  for (const char mode: modes_to_remove)
    if (user->modes.find(mode) != user->modes.end())
      {
        modes += "-"s + mode;
        nb++;
      }
  if (modes.empty())
    return;
  std::vector<std::string> args(nb, nick);
  args.insert(args.begin(), modes);
  irc->send_mode_command(iid.get_local(), args);
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

  irc_responder_callback_t cb = [this, iid, irc, id, from, nick](const std::string& irc_hostname, const IrcMessage& message) -> bool
  {
    if (irc_hostname != iid.get_server())
      return false;

    if (message.command == "MODE" && message.arguments.size() >= 2)
      {
        const std::string& chan_name = message.arguments[0];
        if (chan_name != iid.get_local())
          return false;
        const std::string actor_nick = IrcUser{message.prefix}.nick;
        if (!irc || irc->get_own_nick() != actor_nick)
          return false;

        this->xmpp.send_iq_result(id, from, std::to_string(iid));
      }
    else if (message.command == "401" && message.arguments.size() >= 2)
        {
          const std::string target_later = message.arguments[1];
          if (target_later != nick)
            return false;
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
          this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "item-not-found",
                                        error_message, false);
        }
    else if (message.command == "482" && message.arguments.size() >= 2)
      {
        const std::string chan_name_later = utils::tolower(message.arguments[1]);
        if (chan_name_later != iid.get_local())
          return false;
        std::string error_message = "You're not channel operator";
        if (message.arguments.size() >= 3)
          error_message = message.arguments[2];
        this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
                                     error_message, false);
      }
    else if (message.command == "472" && message.arguments.size() >= 2)
      {
363
          std::string error_message = "Unknown mode: " + message.arguments[1];
364 365 366 367 368 369 370 371
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
          this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
                                        error_message, false);
      }
    return true;
  };
  this->add_waiting_irc(std::move(cb));
372 373
}

louiz’'s avatar
louiz’ committed
374
void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
375
{
louiz’'s avatar
louiz’ committed
376
  if (iid.get_local().empty() || iid.get_server().empty())
377
    {
378
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
379 380 381 382
                                    "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);
383 384
      return;
    }
385
  IrcClient* irc = this->get_irc_client(iid.get_server());
386 387 388 389 390 391
  std::vector<std::string> lines = utils::split(body, '\n', true);
  if (lines.empty())
    return ;
  for (const std::string& line: lines)
    {
      if (line.substr(0, 4) == "/me ")
louiz’'s avatar
louiz’ committed
392
        irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type);
393
      else
louiz’'s avatar
louiz’ committed
394
        irc->send_private_message(iid.get_local(), line, type);
395
    }
396 397
}

louiz’'s avatar
louiz’ committed
398 399 400 401 402 403
void Bridge::send_raw_message(const std::string& hostname, const std::string& body)
{
  IrcClient* irc = this->get_irc_client(hostname);
  irc->send_raw(body);
}

404
void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
405
{
louiz’'s avatar
louiz’ committed
406
  IrcClient* irc = this->get_irc_client(iid.get_server());
407 408 409 410
  const auto key = iid.to_tuple();
  if (!this->is_resource_in_chan(key, resource))
    return ;

411 412
  IrcChannel* channel = irc->get_channel(iid.get_local());

413 414
  const auto resources = this->number_of_resources_in_chan(key);
  if (resources == 1)
415
    {
416 417 418
      // Do not send a PART message if we actually are not in that channel
      // or if we already sent a PART but we are just waiting for the
      // acknowledgment from the server
419 420
      bool persistent = false;
#ifdef USE_DATABASE
421
      const auto goptions = Database::get_global_options(this->user_jid);
422
      if (goptions.col<Database::GlobalPersistent>())
423 424 425 426 427 428
        persistent = true;
      else
        {
          const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, iid.get_server(), iid.get_local());
          persistent = coptions.col<Database::Persistent>();
        }
429 430 431
#endif
      if (channel->joined && !channel->parting && !persistent)
        {
432
          irc->send_part_command(iid.get_local(), status_message);
433
        }
434
      else if (channel->joined)
435
        {
436
          this->send_muc_leave(iid, *channel->get_self(), "", true, true, resource, irc);
437
        }
438 439
      if (persistent)
        this->remove_resource_from_chan(key, resource);
440 441 442 443
      // Since there are no resources left in that channel, we don't
      // want to receive private messages using this room's JID
      this->remove_all_preferred_from_jid_of_room(iid.get_local());
    }
444 445
  else
    {
446
      if (channel && channel->joined)
447
        this->send_muc_leave(iid, *channel->get_self(),
448
                             "Biboumi note: " + std::to_string(resources - 1) + " resources are still in this channel.",
449
                             true, true, resource, irc);
450
      this->remove_resource_from_chan(key, resource);
louiz’'s avatar
louiz’ committed
451
    }
452 453 454
      if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
        this->remove_resource_from_server(iid.get_server(), resource);

455
}
456

457
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
louiz’'s avatar
louiz’ committed
458
{
459 460 461 462
  // We don’t change the nick if the presence was sent to a channel the resource is not in.
  auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), iid.get_server()}, requesting_resource);
  if (!res_in_chan)
    return;
louiz’'s avatar
louiz’ committed
463
  IrcClient* irc = this->get_irc_client(iid.get_server());
464
  irc->send_nick_command(new_nick);
louiz’'s avatar
louiz’ committed
465 466
}

467 468
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
                                           ResultSetInfo rs_info)
469
{
470
  auto& list = this->channel_list_cache[iid.get_server()];
471

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
  // We fetch the list from the IRC server only if we have a complete
  // cached list that needs to be invalidated (that is, when the request
  // doesn’t have a after or before, or when the list is empty).
  // If the list is not complete, this means that a request is already
  // ongoing, so we just need to add the callback.
  // By default the list is complete and empty.
  if (list.complete &&
      (list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty())))
    {
      IrcClient* irc = this->get_irc_client(iid.get_server());
      irc->send_list_command();

      // Add a callback that will populate our list
      list.channels.clear();
      list.complete = false;
      irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname,
                                                const IrcMessage& message) -> bool
      {
        if (irc_hostname != iid.get_server())
          return false;
492

493
        auto& list = this->channel_list_cache[iid.get_server()];
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

        if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
            || message.command == "ERR_NOSUCHSERVER")
          {
            list.complete = true;
            return true;
          }
        else if (message.command == "322" || message.command == "RPL_LIST")
          { // Add element to list
            if (message.arguments.size() == 4)
              {
                list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()),
                                           message.arguments[2], message.arguments[3]);
              }
            return false;
          }
        else if (message.command == "323" || message.command == "RPL_LISTEND")
          { // Send the iq response with the content of the list
            list.complete = true;
            return true;
          }
        return false;
      };
517

518 519 520 521 522 523 524
      this->add_waiting_irc(std::move(cb));
    }

  // If the list is complete, we immediately send the answer.
  // Otherwise, we install a callback, that will populate our list and send
  // the answer when we can.
  if (list.complete)
525
    {
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
      this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
    }
  else
    {
      // Add a callback to answer the request as soon as we can
      irc_responder_callback_t cb = [this, iid, iq_id, to_jid,
                                     rs_info=std::move(rs_info)](const std::string& irc_hostname,
                                                                 const IrcMessage& message) -> bool
      {
        if (irc_hostname != iid.get_server())
          return false;

        if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
            || message.command == "ERR_NOSUCHSERVER")
          {
            std::string text;
            if (message.arguments.size() >= 2)
              text = message.arguments[1];
            this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false);
            return true;
          }
        else if (message.command == "322" || message.command == "RPL_LIST")
          {
            auto& list = channel_list_cache[iid.get_server()];
            const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
            return res;
          }
        else if (message.command == "323" || message.command == "RPL_LISTEND")
          { // Send the iq response with the content of the list
            auto& list = channel_list_cache[iid.get_server()];
            this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
            return true;
          }
559
        return false;
560 561 562 563 564 565 566 567 568 569
      };

      this->add_waiting_irc(std::move(cb));
    }
}

bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info,
                                        const std::string& id, const std::string& to_jid, const std::string& from)
{
  auto begin = channel_list.channels.begin();
570
  auto end = channel_list.channels.end();
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
  if (channel_list.complete)
    {
      begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
      {
        return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
      });
      if (begin == channel_list.channels.end())
        begin = channel_list.channels.begin();
      else
        begin = std::next(begin);
      end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
      {
        return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
      });
      if (rs_info.max >= 0)
586
        {
587 588
          if (std::distance(begin, end) >= rs_info.max)
            end = begin + rs_info.max;
589
        }
590 591 592 593 594 595 596 597 598 599 600 601 602 603
    }
  else
    {
      if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0)
        return false;
      if (!rs_info.after.empty())
        {
          begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
          {
            return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
          });
          if (begin == channel_list.channels.end())
            return false;
          begin = std::next(begin);
604
        }
605 606 607 608 609 610 611 612
        if (!rs_info.before.empty())
        {
          end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
          {
            return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
          });
          if (end == channel_list.channels.end())
            return false;
613
        }
614 615 616 617 618 619 620 621 622 623
      if (rs_info.max >= 0)
        {
          if (std::distance(begin, end) < rs_info.max)
            return false;
          else
            end = begin + rs_info.max;
        }
    }
  this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info);
  return true;
624 625
}

626 627
void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
                           const std::string& iq_id, const std::string& to_jid)
628
{
louiz’'s avatar
louiz’ committed
629
  IrcClient* irc = this->get_irc_client(iid.get_server());
630 631 632 633

  irc->send_kick_command(iid.get_local(), target, reason);
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, iid](const std::string& irc_hostname,
                                                                   const IrcMessage& message) -> bool
634
    {
635 636 637 638 639 640 641
      if (irc_hostname != iid.get_server())
        return false;
      if (message.command == "KICK" && message.arguments.size() >= 2)
        {
          const std::string target_later = message.arguments[1];
          const std::string chan_name_later = utils::tolower(message.arguments[0]);
          if (target_later != target || chan_name_later != iid.get_local())
642
            return false;
643
          this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid));
644 645 646 647 648 649 650 651 652
        }
      else if (message.command == "401" && message.arguments.size() >= 2)
        {
          const std::string target_later = message.arguments[1];
          if (target_later != target)
            return false;
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
653
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found",
654 655 656 657 658 659 660 661 662 663
                                        error_message, false);
        }
      else if (message.command == "482" && message.arguments.size() >= 2)
        {
          const std::string chan_name_later = utils::tolower(message.arguments[1]);
          if (chan_name_later != iid.get_local())
            return false;
          std::string error_message = "You're not channel operator";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
664
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed",
665 666 667 668 669
                                        error_message, false);
        }
      return true;
    };
  this->add_waiting_irc(std::move(cb));
670 671
}

louiz’'s avatar
louiz’ committed
672
void Bridge::set_channel_topic(const Iid& iid, std::string subject)
673
{
louiz’'s avatar
louiz’ committed
674
  IrcClient* irc = this->get_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
675 676 677
  std::string::size_type pos{0};
  while ((pos = subject.find('\n', pos)) != std::string::npos)
    subject[pos] = ' ';
678
  irc->send_topic_command(iid.get_local(), subject);
679 680
}

louiz’'s avatar
louiz’ committed
681 682 683 684
void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os)
{
  std::string result(name + " " + version + " " + os);

685
  this->send_private_message(iid, "\01VERSION " + result + "\01", "NOTICE");
louiz’'s avatar
louiz’ committed
686 687
}

688 689
void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id)
{
690
  this->send_private_message(iid, "\01PING " + utils::revstr(id) + "\01", "NOTICE");
691 692 693 694 695 696
}

void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick,
                                        const std::string& iq_id, const std::string& to_jid,
                                        const std::string& from_jid)
{
697
  Iid iid(nick, irc_hostname, Iid::Type::User);
698 699
  this->send_private_message(iid, "\01PING " + iq_id + "\01");

louiz’'s avatar
louiz’ committed
700 701
  irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid]
          (const std::string& hostname, const IrcMessage& message) -> bool
702
    {
703
      if (irc_hostname != hostname || message.arguments.size() < 2)
704 705 706
        return false;
      IrcUser user(message.prefix);
      const std::string body = message.arguments[1];
707 708
      if (message.command == "NOTICE" && utils::tolower(user.nick) == nick
          && body.substr(0, 6) == "\01PING ")
709
        {
710
          const std::string id = body.substr(6, body.size() - 7);
711 712
          if (id != iq_id)
            return false;
713
          this->xmpp.send_iq_result_full_jid(iq_id, to_jid, from_jid);
714 715
          return true;
        }
716
      if (message.command == "401" && message.arguments[1] == nick)
717 718 719 720
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
721
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
722 723 724 725 726 727 728 729 730
                                        error_message, true);
          return true;
        }

      return false;
    };
  this->add_waiting_irc(std::move(cb));
}

731 732 733 734
void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string& nick,
                                               const std::string& iq_id, const std::string& to_jid,
                                               const std::string& from_jid)
{
735
  Jid from(to_jid);
736 737
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
738
  if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource))
739
    {
740
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
741 742 743 744 745
                                    "", true);
      return;
    }
  if (chan->get_self()->nick != nick && !chan->find_user(nick))
    {
746
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
747 748 749 750 751 752 753 754
                                    "Recipient not in room", true);
      return;
    }

  // The user is in the room, send it a direct PING
  this->send_irc_user_ping_request(iid.get_server(), nick, iq_id, to_jid, from_jid);
}

755 756 757 758
void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid,
                             const std::string& from_jid)
{
  Jid jid(from_jid);
759
  if (irc_hostname.empty() || this->find_irc_client(irc_hostname))
760
    this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
761
  else
762
    this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
763 764 765
                                  "", true);
}

766
void Bridge::send_irc_invitation(const Iid& iid, const std::string& to)
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  Jid to_jid(to);
  std::string target_nick;
  // Many ways to address a nick:
  // A jid (ANY jid…) with a resource
  if (!to_jid.resource.empty())
    target_nick = to_jid.resource;
  else if (!to_jid.local.empty()) // A jid with a iid with a local part
    target_nick = Iid(to_jid.local, {}).get_local();
  else
    target_nick = to; // Not a jid, just the nick
  irc->send_invitation(iid.get_local(), target_nick);
}

782 783 784 785
void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target,
                                      const std::string& iq_id, const std::string& to_jid,
                                      const std::string& from_jid)
{
786
  Iid iid(target, irc_hostname, Iid::Type::User);
787 788 789
  this->send_private_message(iid, "\01VERSION\01");
  // TODO, add a timer to remove that waiting iq if the server does not
  // respond with a matching command before n seconds
louiz’'s avatar
louiz’ committed
790 791
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid]
          (const std::string& hostname, const IrcMessage& message) -> bool
792
    {
793 794 795
      if (irc_hostname != hostname)
        return false;
      IrcUser user(message.prefix);
796
      if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) &&
797 798 799 800
          message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ")
        {
          // remove the "\01VERSION " and the "\01" parts from the string
          const std::string version = message.arguments[1].substr(9, message.arguments[1].size() - 10);
801
          this->xmpp.send_version(iq_id, to_jid, from_jid, version);
802 803 804 805 806 807 808 809
          return true;
        }
      if (message.command == "401" && message.arguments.size() >= 2
          && message.arguments[1] == target)
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
810
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
811 812 813 814
                                        error_message, true);
          return true;
        }
      return false;
815 816
    };
  this->add_waiting_irc(std::move(cb));
817 818
}

819
void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc)
louiz’'s avatar
louiz’ committed
820
{
821
  const auto encoding = in_encoding_for(*this, iid);
822
  std::string uuid{};
823
  if (muc)
824
    {
825
#ifdef USE_DATABASE
826
      const auto xmpp_body = this->make_xmpp_body(body, encoding);
827
      if (!nick.empty() && this->record_history)
828 829
        uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
                                           std::get<0>(xmpp_body), nick);
830
#endif
831
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
832
        {
833
          this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding),
834
                                      this->user_jid + "/" + resource, uuid, utils::gen_uuid());
835 836
        }
    }
837
  else
838
    {
839
      const auto it = this->preferred_user_from.find(iid.get_local());
840 841
      if (it != this->preferred_user_from.end())
        {
842
          const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
843 844
          for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}])
            this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding),
845 846
                                    this->user_jid + "/"
                                    + resource, "chat", true, true, true);
847
        }
848
      else
849
        {
850 851
          for (const auto& resource: this->resources_in_server[iid.get_server()])
            this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding),
852
                                    this->user_jid + "/" + resource, "chat", false, true);
853
        }
854
    }
louiz’'s avatar
louiz’ committed
855 856
}

857 858 859
void Bridge::send_presence_error(const Iid& iid, const std::string& nick,
                                 const std::string& type, const std::string& condition,
                                 const std::string& error_code, const std::string& text)
860
{
861
  this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
862 863
}

864
void Bridge::send_muc_leave(const Iid& iid, const IrcUser& user,
865
                            const std::string& message, const bool self,
866
                            const bool user_requested,
867 868
                            const std::string& resource,
                            const IrcClient* client)
869
{
870 871 872 873
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user.get_most_significant_mode(client->get_sorted_user_modes()));

874
  if (!resource.empty())
875 876
    this->xmpp.send_muc_leave(std::to_string(iid), user.nick, this->make_xmpp_body(message),
                              this->user_jid + "/" + resource, self, user_requested, affiliation, role);
877
  else
878 879
    {
      for (const auto &res: this->resources_in_chan[iid.to_tuple()])
880 881
        this->xmpp.send_muc_leave(std::to_string(iid), user.nick, this->make_xmpp_body(message),
                                  this->user_jid + "/" + res, self, user_requested, affiliation, role);
882
      if (self)
883 884 885 886 887 888 889 890 891 892 893 894 895
        {
          // Copy the resources currently in that channel
          const auto resources_in_chan = this->resources_in_chan[iid.to_tuple()];

          this->remove_all_resources_from_chan(iid.to_tuple());

          // Now, for each resource that was in that channel, remove it from the server if it’s
          // not in any other channel
          for (const auto& r: resources_in_chan)
          if (this->number_of_channels_the_resource_is_in(iid.get_server(), r) == 0)
            this->remove_resource_from_server(iid.get_server(), r);

        }
896 897

    }
898
  IrcClient* irc = this->find_irc_client(iid.get_server());
899
  if (self && irc && irc->number_of_joined_channels() == 0)
900
    irc->send_quit_command("");
901 902
}

903 904 905 906 907
void Bridge::send_nick_change(Iid&& iid,
                              const std::string& old_nick,
                              const std::string& new_nick,
                              const char user_mode,
                              const bool self)
louiz’'s avatar
louiz’ committed
908
{
909 910 911 912
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

913
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
914 915
    this->xmpp.send_nick_change(std::to_string(iid),
                                old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self);
louiz’'s avatar
louiz’ committed
916 917
}

918 919
void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg)
{
louiz’'s avatar
louiz’ committed
920 921
  std::string body;
  if (!author.empty())
922 923
    {
      IrcUser user(author);
924
      body = "\u000303" + user.nick + (user.host.empty()?
louiz’'s avatar
louiz’ committed
925 926
                                        "\u0003: ":
                                        (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg;
927
    }
louiz’'s avatar
louiz’ committed
928 929
  else
    body = msg;
930

931
  const auto encoding = in_encoding_for(*this, {from, this});
932 933
  for (const auto& resource: this->resources_in_server[from])
    {
934
      if (Config::get("fixed_irc_server", "").empty())
935
        this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true);
936
      else
937
        this->xmpp.send_message("", this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true);
938
    }
939 940
}

941 942 943
void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
                            const IrcUser* user, const char user_mode, const bool self)
{
944 945 946 947 948 949 950 951
  const auto resources = this->resources_in_chan[ChannelKey{chan_name, hostname}];
  if (self && resources.empty())
    { // This was a forced join: no client ever asked to join this room,
      // but the server tells us we are in that room anyway.  XMPP can’t
      // do that, so we invite all the resources to join that channel.
      const Iid iid(chan_name, hostname, Iid::Type::Channel);
      this->send_xmpp_invitation(iid, "");
    }
952
  else
953 954 955 956
    {
      for (const auto& resource: resources)
        this->send_user_join(hostname, chan_name, user, user_mode, self, resource);
    }
957 958 959 960 961
}

void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
                       const IrcUser* user, const char user_mode,
                       const bool self, const std::string& resource)
962
{
963 964 965 966
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

967 968 969 970
  std::string encoded_chan_name(chan_name);
  xep0106::encode(encoded_chan_name);

  this->xmpp.send_user_join(encoded_chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
971
                            affiliation, role, this->user_jid + "/" + resource, self);
972 973
}

louiz’'s avatar
louiz’ committed
974 975
void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic,
                        const std::string& who)
976
{
977 978
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    {
979
      this->send_topic(hostname, chan_name, topic, who, resource);
980
    }
981
}
louiz’'s avatar
louiz’ committed
982

983 984 985 986
void Bridge::send_topic(const std::string& hostname, const std::string& chan_name,
                        const std::string& topic, const std::string& who,
                        const std::string& resource)
{
987 988
  std::string encoded_chan_name(chan_name);
  xep0106::encode(encoded_chan_name);
989
  const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel});
990
  this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server(
991 992 993 994
      "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);

}

995
void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit)
996 997
{
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
998
    this->send_room_history(hostname, chan_name, resource, history_limit);
999 1000
}

1001
void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit)
1002 1003 1004
{
#ifdef USE_DATABASE
  const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
1005 1006 1007
  auto limit = coptions.col<Database::MaxHistoryLength>();
  if (history_limit.stanzas >= 0 && history_limit.stanzas < limit)
    limit = history_limit.stanzas;
1008
  const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, limit, history_limit.since, {}, Id::unset_value, Database::Paging::last);
1009
  chan_name.append(utils::empty_if_fixed_server("%" + hostname));
1010 1011
  for (const auto& line: lines)
    {
louiz’'s avatar
louiz’ committed
1012
      const DateTime& datetime = line.col<Database::Date>();
1013
      this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(),
louiz’'s avatar
louiz’ committed
1014
                                      this->user_jid + "/" + resource, datetime);
1015
    }
1016 1017 1018 1019
#else
  (void)hostname;
  (void)chan_name;
  (void)resource;
1020
  (void)history_limit;
1021 1022 1023
#endif
}

louiz’'s avatar
louiz’ committed
1024 1025
std::string Bridge::get_own_nick(const Iid& iid)
{
1026
  IrcClient* irc = this->find_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
1027 1028
  if (irc)
    return irc->get_own_nick();
louiz’'s avatar
louiz’ committed
1029
  return {};
louiz’'s avatar
louiz’ committed
1030
}
1031

1032
size_t Bridge::active_clients() const
louiz’'s avatar
louiz’ committed
1033 1034 1035 1036
{
  return this->irc_clients.size();
}

1037 1038
void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author,
                           const bool self)
1039
{
1040
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
1041
      this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource, self);
1042
}
1043 1044 1045

void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
{
louiz’'s avatar
louiz’ committed
1046 1047 1048
    for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
        this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource,
                                       "cancel", "conflict", "409", "");
1049
}
1050 1051 1052 1053 1054

void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
{
  std::string role;
  std::string affiliation;
1055 1056

  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
1057
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
louiz’'s avatar
louiz’ committed
1058 1059
    this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role,
                                            this->user_jid + "/" + resource);
1060
}
1061 1062 1063

void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
{
1064 1065
  const auto resources = this->resources_in_server[hostname];
  if (resources.begin() != resources.end())
1066
    this->xmpp.send_iq_version_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname),
louiz’'s avatar
louiz’ committed
1067
                                       this->user_jid + "/" + *resources.begin());
1068
}
1069

1070 1071 1072 1073
void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,
                                    const std::string& id)
{
  // Use revstr because the forwarded ping to target XMPP user must not be
louiz’'s avatar
louiz’ committed