bridge.cpp 47.1 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>
louiz’'s avatar
louiz’ committed
8
#include <logger/logger.hpp>
9
#include <utils/revstr.hpp>
louiz’'s avatar
louiz’ committed
10
#include <utils/split.hpp>
11
#include <xmpp/jid.hpp>
12
#include <database/database.hpp>
13
#include "result_set_management.hpp"
louiz’'s avatar
louiz’ committed
14
#include <algorithm>
louiz’'s avatar
louiz’ committed
15

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

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

20 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();
  auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
26 27 28
  auto result = options.col<Database::EncodingIn>();
  if (!result.empty())
    return result;
29
#else
30 31
  (void)bridge;
  (void)iid;
32
#endif
33
  return {"ISO-8859-1"};
34 35
}

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

47
/**
48 49
 * Return the role and affiliation, corresponding to the given irc mode
 */
50 51
static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(const char mode)
{
52 53
  if (mode == 'a' || mode == 'q'){
    return std::make_tuple("moderator", "owner");}
54 55 56 57 58 59 60 61 62 63
  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");
}

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

73 74 75 76 77 78 79 80 81 82 83 84 85 86
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
87 88 89 90 91 92
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
93 94
    if (!client->is_connected() && !client->is_connecting() &&
        !client->get_resolver().is_resolving())
louiz’'s avatar
louiz’ committed
95 96 97 98 99 100
      it = this->irc_clients.erase(it);
    else
      ++it;
  }
}

101 102 103 104 105
const std::string& Bridge::get_jid() const
{
  return this->user_jid;
}

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

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

122
IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname)
123 124 125 126 127 128 129
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
130 131
      auto username = nickname;
      auto realname = nickname;
louiz’'s avatar
louiz’ committed
132
      Jid jid(this->user_jid);
133 134 135 136 137 138 139 140
      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
141
                                                            realname, jid.domain,
142
                                                            *this));
143 144 145 146 147
      std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
      return irc.get();
    }
}

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

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

172 173 174
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
                              const std::string& resource)
{
louiz’'s avatar
louiz’ committed
175
  const auto& hostname = iid.get_server();
176 177 178 179 180
  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);
louiz’'s avatar
louiz’ committed
181
  if (iid.get_local().empty())
louiz’'s avatar
louiz’ committed
182 183 184
    { // Join the dummy channel
      if (irc->is_welcomed())
        {
185
          if (res_in_chan)
louiz’'s avatar
louiz’ committed
186 187 188
            return false;
          // Immediately simulate a message coming from the IRC server saying that we
          // joined the channel
189 190 191 192 193 194 195 196 197 198 199 200 201
          if (irc->get_dummy_channel().joined)
            {
              this->generate_channel_join_for_resource(iid, resource);
            }
          else
            {
              const IrcMessage join_message(irc->get_nick(), "JOIN", {""});
              irc->on_channel_join(join_message);
              const IrcMessage end_join_message(std::string(iid.get_server()), "366",
                                                {irc->get_nick(),
                                                 "", "End of NAMES list"});
              irc->on_channel_completely_joined(end_join_message);
            }
louiz’'s avatar
louiz’ committed
202 203
        }
      else
204
        {
louiz’'s avatar
louiz’ committed
205
          irc->get_dummy_channel().joining = true;
206 207
          irc->start();
        }
louiz’'s avatar
louiz’ committed
208 209
      return true;
    }
louiz’'s avatar
louiz’ committed
210
  if (irc->is_channel_joined(iid.get_local()) == false)
louiz’'s avatar
louiz’ committed
211
    {
212
      irc->send_join_command(iid.get_local(), password);
louiz’'s avatar
louiz’ committed
213
      return true;
214 215
    } else if (!res_in_chan) {
      this->generate_channel_join_for_resource(iid, resource);
louiz’'s avatar
louiz’ committed
216 217
    }
  return false;
218 219
}

louiz’'s avatar
louiz’ committed
220 221
void Bridge::send_channel_message(const Iid& iid, const std::string& body)
{
222
  if (iid.get_server().empty())
louiz’'s avatar
louiz’ committed
223
    {
224
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
225 226 227 228 229
        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
230 231
      return;
    }
louiz’'s avatar
louiz’ committed
232
  IrcClient* irc = this->get_irc_client(iid.get_server());
233

234 235 236 237 238 239 240 241 242 243 244 245
  // 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 ;
  for (const std::string& line: lines)
louiz’'s avatar
louiz’ committed
246
    {
louiz’'s avatar
louiz’ committed
247
      if (line.substr(0, 5) == "/mode")
248
        {
louiz’'s avatar
louiz’ committed
249
          std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
louiz’'s avatar
louiz’ committed
250
          irc->send_mode_command(iid.get_local(), args);
251 252 253 254
          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
255
        irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
256
      else
louiz’'s avatar
louiz’ committed
257
        irc->send_channel_message(iid.get_local(), line);
258

259
      std::string uuid;
260
#ifdef USE_DATABASE
261
      const auto xmpp_body = this->make_xmpp_body(line);
262
      if (this->record_history)
263
        uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
264
                                    std::get<0>(xmpp_body), irc->get_own_nick());
265
#endif
266
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
267 268
        this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
                                    this->user_jid + "/" + resource, uuid);
louiz’'s avatar
louiz’ committed
269
    }
louiz’'s avatar
louiz’ committed
270 271
}

272 273
void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& from,
                                             const std::string& nick,
274
                                             const std::string& affiliation,
275 276
                                             const std::string& role,
                                             const std::string& id)
277 278 279 280 281 282 283
{
  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)
284 285 286 287 288
    {
      this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel",
                                   "item-not-found", "no such nick", false);
      return;
    }
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 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
  // 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);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380

  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)
      {
381
          std::string error_message = "Unknown mode: " + message.arguments[1];
382 383 384 385 386 387 388 389
          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));
390 391
}

louiz’'s avatar
louiz’ committed
392
void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
393
{
louiz’'s avatar
louiz’ committed
394
  if (iid.get_local().empty() || iid.get_server().empty())
395
    {
396
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
397 398 399 400
                                    "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);
401 402
      return;
    }
403
  IrcClient* irc = this->get_irc_client(iid.get_server());
404 405 406 407 408 409
  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
410
        irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type);
411
      else
louiz’'s avatar
louiz’ committed
412
        irc->send_private_message(iid.get_local(), line, type);
413
    }
louiz’'s avatar
louiz’ committed
414 415
}

louiz’'s avatar
louiz’ committed
416 417 418 419 420 421
void Bridge::send_raw_message(const std::string& hostname, const std::string& body)
{
  IrcClient* irc = this->get_irc_client(hostname);
  irc->send_raw(body);
}

422
void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
louiz’'s avatar
louiz’ committed
423
{
louiz’'s avatar
louiz’ committed
424
  IrcClient* irc = this->get_irc_client(iid.get_server());
425 426 427 428
  const auto key = iid.to_tuple();
  if (!this->is_resource_in_chan(key, resource))
    return ;

louiz’'s avatar
louiz’ committed
429 430
  IrcChannel* channel = irc->get_channel(iid.get_local());

431 432
  const auto resources = this->number_of_resources_in_chan(key);
  if (resources == 1)
433
    {
434 435 436
      // 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
louiz’'s avatar
louiz’ committed
437 438
      bool persistent = false;
#ifdef USE_DATABASE
439 440 441 442 443 444 445 446
      const auto goptions = Database::get_global_options(this->user_jid);
      if (goptions.col<Database::Persistent>())
        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>();
        }
louiz’'s avatar
louiz’ committed
447 448 449
#endif
      if (channel->joined && !channel->parting && !persistent)
        {
louiz’'s avatar
louiz’ committed
450
          const auto& chan_name = iid.get_local();
louiz’'s avatar
louiz’ committed
451 452 453 454 455
          if (chan_name.empty())
            irc->leave_dummy_channel(status_message, resource);
          else
            irc->send_part_command(iid.get_local(), status_message);
        }
456
      else if (channel->joined)
louiz’'s avatar
louiz’ committed
457
        {
458
          this->send_muc_leave(iid, channel->get_self()->nick, "", true, true, resource);
louiz’'s avatar
louiz’ committed
459
        }
460 461 462 463
      // 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());
    }
464 465
  else
    {
466 467
      if (channel && channel->joined)
        this->send_muc_leave(iid, channel->get_self()->nick,
468
                             "Biboumi note: " + std::to_string(resources - 1) + " resources are still in this channel.",
469
                             true, true, resource);
louiz’'s avatar
louiz’ committed
470 471 472
      this->remove_resource_from_chan(key, resource);
      if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
        this->remove_resource_from_server(iid.get_server(), resource);
473
    }
louiz’'s avatar
louiz’ committed
474

475
}
louiz’'s avatar
louiz’ committed
476

477
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
louiz’'s avatar
louiz’ committed
478
{
479 480 481 482
  // 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
483
  IrcClient* irc = this->get_irc_client(iid.get_server());
484
  irc->send_nick_command(new_nick);
louiz’'s avatar
louiz’ committed
485 486
}

487 488
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
                                           ResultSetInfo rs_info)
489
{
490
  auto& list = this->channel_list_cache[iid.get_server()];
491

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
  // 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;
512

513
        auto& list = this->channel_list_cache[iid.get_server()];
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536

        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;
      };
537

538 539 540 541 542 543 544
      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)
545
    {
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
      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;
          }
579
        return false;
580 581 582 583 584 585 586 587 588 589
      };

      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();
590
  auto end = channel_list.channels.end();
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
  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)
606
        {
607 608
          if (std::distance(begin, end) >= rs_info.max)
            end = begin + rs_info.max;
609
        }
610 611 612 613 614 615 616 617 618 619 620 621 622 623
    }
  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);
624
        }
625 626 627 628 629 630 631 632
        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;
633
        }
634 635 636 637 638 639 640 641 642 643
      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;
644 645
}

646 647
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)
648
{
louiz’'s avatar
louiz’ committed
649
  IrcClient* irc = this->get_irc_client(iid.get_server());
650 651 652 653

  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
654
    {
655 656 657 658 659 660 661
      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())
662
            return false;
663
          this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid));
664 665 666 667 668 669 670 671 672
        }
      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];
673
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found",
674 675 676 677 678 679 680 681 682 683
                                        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];
684
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed",
685 686 687 688 689
                                        error_message, false);
        }
      return true;
    };
  this->add_waiting_irc(std::move(cb));
690 691
}

louiz’'s avatar
louiz’ committed
692
void Bridge::set_channel_topic(const Iid& iid, std::string subject)
693
{
louiz’'s avatar
louiz’ committed
694
  IrcClient* irc = this->get_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
695 696 697
  std::string::size_type pos{0};
  while ((pos = subject.find('\n', pos)) != std::string::npos)
    subject[pos] = ' ';
698
  irc->send_topic_command(iid.get_local(), subject);
699 700
}

louiz’'s avatar
louiz’ committed
701 702 703 704
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);

705
  this->send_private_message(iid, "\01VERSION " + result + "\01", "NOTICE");
louiz’'s avatar
louiz’ committed
706 707
}

708 709
void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id)
{
710
  this->send_private_message(iid, "\01PING " + utils::revstr(id) + "\01", "NOTICE");
711 712 713 714 715 716
}

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)
{
717
  Iid iid(nick, irc_hostname, Iid::Type::User);
718 719
  this->send_private_message(iid, "\01PING " + iq_id + "\01");

louiz’'s avatar
louiz’ committed
720 721
  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
722
    {
louiz’'s avatar
louiz’ committed
723
      if (irc_hostname != hostname || message.arguments.size() < 2)
724 725 726
        return false;
      IrcUser user(message.prefix);
      const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
727 728
      if (message.command == "NOTICE" && utils::tolower(user.nick) == nick
          && body.substr(0, 6) == "\01PING ")
729
        {
730
          const std::string id = body.substr(6, body.size() - 7);
731 732
          if (id != iq_id)
            return false;
733
          this->xmpp.send_iq_result_full_jid(iq_id, to_jid, from_jid);
734 735
          return true;
        }
louiz’'s avatar
louiz’ committed
736
      if (message.command == "401" && message.arguments[1] == nick)
737 738 739 740
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
741
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
742 743 744 745 746 747 748 749 750
                                        error_message, true);
          return true;
        }

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

751 752 753 754
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)
{
755
  Jid from(to_jid);
756 757
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
758
  if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource))
759
    {
760
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
761 762 763 764 765
                                    "", true);
      return;
    }
  if (chan->get_self()->nick != nick && !chan->find_user(nick))
    {
766
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
767 768 769 770 771 772 773 774
                                    "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);
}

775 776 777 778
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);
779
  if (irc_hostname.empty() || this->find_irc_client(irc_hostname))
780
    this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
781
  else
782
    this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
783 784 785
                                  "", true);
}

786
void Bridge::send_irc_invitation(const Iid& iid, const std::string& to)
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
{
  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);
}

802 803 804 805
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)
{
806
  Iid iid(target, irc_hostname, Iid::Type::User);
807 808 809
  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
810 811
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid]
          (const std::string& hostname, const IrcMessage& message) -> bool
812
    {
813 814 815
      if (irc_hostname != hostname)
        return false;
      IrcUser user(message.prefix);
816
      if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) &&
817 818 819 820
          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);
821
          this->xmpp.send_version(iq_id, to_jid, from_jid, version);
822 823 824 825 826 827 828 829
          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];
830
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
831 832 833 834
                                        error_message, true);
          return true;
        }
      return false;
835 836
    };
  this->add_waiting_irc(std::move(cb));
837 838
}

louiz’'s avatar
louiz’ committed
839
void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc)
louiz’'s avatar
louiz’ committed
840
{
841
  const auto encoding = in_encoding_for(*this, iid);
louiz’'s avatar
louiz’ committed
842
  if (muc)
843
    {
844
#ifdef USE_DATABASE
845
      const auto xmpp_body = this->make_xmpp_body(body, encoding);
846
      if (!nick.empty() && this->record_history)
847
        Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
848
                                    std::get<0>(xmpp_body), nick);
849
#endif
850
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
851
        {
852 853
          this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding),
                                      this->user_jid + "/" + resource, {});
854

855 856
        }
    }
louiz’'s avatar
louiz’ committed
857
  else
858 859
    {
      std::string target = std::to_string(iid);
860
      const auto it = this->preferred_user_from.find(iid.get_local());
861 862
      if (it != this->preferred_user_from.end())
        {
863
          const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
864 865
          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),
866
                                    this->user_jid + "/" + resource, "chat", true, true);
867
        }
868
      else
869
        {
870 871
          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),
872
                                    this->user_jid + "/" + resource, "chat", false, true);
873
        }
874
    }
louiz’'s avatar
louiz’ committed
875 876
}

877 878 879
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)
880
{
881
  this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
882 883
}

884 885
void Bridge::send_muc_leave(const Iid& iid, const std::string& nick,
                            const std::string& message, const bool self,
886
                            const bool user_requested,
louiz’'s avatar
louiz’ committed
887
                            const std::string& resource)
louiz’'s avatar
louiz’ committed
888
{
889
  if (!resource.empty())
louiz’'s avatar
louiz’ committed
890
    this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message),
891
                              this->user_jid + "/" + resource, self, user_requested);
892
  else
louiz’'s avatar
louiz’ committed
893 894
    {
      for (const auto &res: this->resources_in_chan[iid.to_tuple()])
louiz’'s avatar
louiz’ committed
895
        this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message),
896
                                  this->user_jid + "/" + res, self, user_requested);
897 898
      if (self)
        this->remove_all_resources_from_chan(iid.to_tuple());
louiz’'s avatar
louiz’ committed
899 900

    }
901
  IrcClient* irc = this->find_irc_client(iid.get_server());
902
  if (self && irc && irc->number_of_joined_channels() == 0)
903
    irc->send_quit_command("");
louiz’'s avatar
louiz’ committed
904 905
}

906 907 908 909 910
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
911
{
912 913 914 915
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

916
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
917 918
    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
919 920
}

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

934
  const auto encoding = in_encoding_for(*this, {from, this});
935 936
  for (const auto& resource: this->resources_in_server[from])
    {
937
      this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false);
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 952 953 954 955 956
  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, "");
    }
    else
    {
      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 996 997 998 999 1000
void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name)
{
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    this->send_room_history(hostname, chan_name, resource);
}

1001
void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource)