bridge.cpp 36.5 KB
Newer Older
1
#include <bridge/bridge.hpp>
2
#include <bridge/list_element.hpp>
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>
louiz’'s avatar
louiz’ committed
13

louiz’'s avatar
louiz’ committed
14 15
using namespace std::string_literals;

16 17
static const char* action_prefix = "\01ACTION ";

18 19 20 21 22 23 24 25 26 27 28 29

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());
  return options.encodingIn.value();
#else
  return {"ISO-8859-1"};
#endif
}

30
Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller):
31 32 33 34 35 36
  user_jid(user_jid),
  xmpp(xmpp),
  poller(poller)
{
}

37
/**
38 39
 * Return the role and affiliation, corresponding to the given irc mode
 */
40 41
static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(const char mode)
{
42 43
  if (mode == 'a' || mode == 'q'){
    return std::make_tuple("moderator", "owner");}
44 45 46 47 48 49 50 51 52 53
  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");
}

54
void Bridge::shutdown(const std::string& exit_message)
louiz’'s avatar
louiz’ committed
55 56 57
{
  for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it)
  {
louiz’'s avatar
louiz’ committed
58 59
    it->second->send_quit_command(exit_message);
    it->second->leave_dummy_channel(exit_message);
louiz’'s avatar
louiz’ committed
60 61 62
  }
}

63 64 65 66 67 68 69 70 71 72 73 74 75 76
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
77 78 79 80 81 82
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
83 84
    if (!client->is_connected() && !client->is_connecting() &&
        !client->get_resolver().is_resolving())
louiz’'s avatar
louiz’ committed
85 86 87 88 89 90
      it = this->irc_clients.erase(it);
    else
      ++it;
  }
}

91 92 93 94 95
const std::string& Bridge::get_jid() const
{
  return this->user_jid;
}

louiz’'s avatar
louiz’ committed
96 97 98 99 100 101
std::string Bridge::get_bare_jid() const
{
  Jid jid(this->user_jid);
  return jid.local + "@" + jid.domain;
}

102
Xmpp::body Bridge::make_xmpp_body(const std::string& str, const std::string& encoding)
103 104 105 106 107
{
  std::string res;
  if (utils::is_valid_utf8(str.c_str()))
    res = str;
  else
108
    res = utils::convert_to_utf8(str, encoding.data());
109
  return irc_format_to_xhtmlim(res);
110 111
}

112
IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname)
113 114 115 116 117 118 119
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
120 121
      auto username = nickname;
      auto realname = nickname;
louiz’'s avatar
louiz’ committed
122
      Jid jid(this->user_jid);
123 124 125 126 127 128 129 130
      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
131
                                                            realname, jid.domain,
132
                                                            *this));
133 134 135 136 137
      std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
      return irc.get();
    }
}

louiz’'s avatar
louiz’ committed
138
IrcClient* Bridge::get_irc_client(const std::string& hostname)
139 140 141 142 143 144 145 146 147 148 149
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
      throw IRCNotConnected(hostname);
    }
}

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

162 163 164 165 166 167 168 169 170
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
                              const std::string& resource)
{
  const auto hostname = iid.get_server();
  IrcClient* irc = this->make_irc_client(hostname, nickname);
  this->add_resource_to_server(hostname, resource);
  auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
  if (!res_in_chan)
    this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource);
louiz’'s avatar
louiz’ committed
171
  if (iid.get_local().empty())
louiz’'s avatar
louiz’ committed
172 173 174 175 176 177 178 179 180
    { // Join the dummy channel
      if (irc->is_welcomed())
        {
          if (irc->get_dummy_channel().joined)
            return false;
          // Immediately simulate a message coming from the IRC server saying that we
          // joined the channel
          const IrcMessage join_message(irc->get_nick(), "JOIN", {""});
          irc->on_channel_join(join_message);
louiz’'s avatar
louiz’ committed
181
          const IrcMessage end_join_message(std::string(iid.get_server()), "366",
louiz’'s avatar
louiz’ committed
182 183 184 185 186
                                            {irc->get_nick(),
                                                "", "End of NAMES list"});
          irc->on_channel_completely_joined(end_join_message);
        }
      else
187
        {
louiz’'s avatar
louiz’ committed
188
          irc->get_dummy_channel().joining = true;
189 190
          irc->start();
        }
louiz’'s avatar
louiz’ committed
191 192
      return true;
    }
louiz’'s avatar
louiz’ committed
193
  if (irc->is_channel_joined(iid.get_local()) == false)
louiz’'s avatar
louiz’ committed
194
    {
195
      irc->send_join_command(iid.get_local(), password);
louiz’'s avatar
louiz’ committed
196
      return true;
197 198
    } else if (!res_in_chan) {
      this->generate_channel_join_for_resource(iid, resource);
louiz’'s avatar
louiz’ committed
199 200
    }
  return false;
201 202
}

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

217 218 219 220 221 222 223 224 225 226 227 228
  // 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
229
    {
louiz’'s avatar
louiz’ committed
230
      if (line.substr(0, 5) == "/mode")
231
        {
louiz’'s avatar
louiz’ committed
232
          std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
louiz’'s avatar
louiz’ committed
233
          irc->send_mode_command(iid.get_local(), args);
234 235 236 237
          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
238
        irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
239
      else
louiz’'s avatar
louiz’ committed
240
        irc->send_channel_message(iid.get_local(), line);
241

242
#ifdef USE_DATABASE
243 244 245
      const auto xmpp_body = this->make_xmpp_body(line);
      Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
                                  std::get<0>(xmpp_body), irc->get_own_nick());
246
#endif
247
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
248 249
        this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
                                    this->make_xmpp_body(line), this->user_jid + "/" + resource);
louiz’'s avatar
louiz’ committed
250
    }
louiz’'s avatar
louiz’ committed
251 252
}

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 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
void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& nick,
                                             const std::string& affiliation,
                                             const std::string& role)
{
  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)
    return;
  // 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);
}

louiz’'s avatar
louiz’ committed
317
void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
318
{
louiz’'s avatar
louiz’ committed
319
  if (iid.get_local().empty() || iid.get_server().empty())
320
    {
321
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
322 323 324 325
                                    "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);
326 327
      return;
    }
328
  IrcClient* irc = this->get_irc_client(iid.get_server());
329 330 331 332 333 334
  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
335
        irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type);
336
      else
louiz’'s avatar
louiz’ committed
337
        irc->send_private_message(iid.get_local(), line, type);
338
    }
louiz’'s avatar
louiz’ committed
339 340
}

louiz’'s avatar
louiz’ committed
341 342 343 344 345 346
void Bridge::send_raw_message(const std::string& hostname, const std::string& body)
{
  IrcClient* irc = this->get_irc_client(hostname);
  irc->send_raw(body);
}

347
void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
louiz’'s avatar
louiz’ committed
348
{
louiz’'s avatar
louiz’ committed
349
  IrcClient* irc = this->get_irc_client(iid.get_server());
350 351 352 353 354 355
  const auto key = iid.to_tuple();
  if (!this->is_resource_in_chan(key, resource))
    return ;

  const auto resources = this->number_of_resources_in_chan(key);
  if (resources == 1)
356 357 358 359 360 361
    {
      irc->send_part_command(iid.get_local(), status_message);
      // 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());
    }
362 363 364 365 366 367 368 369 370 371
  else
    {
      IrcChannel* chan = irc->get_channel(iid.get_local());
      if (chan)
        {
          auto nick = chan->get_self()->nick;
          this->remove_resource_from_chan(key, resource);
          this->send_muc_leave(std::move(iid), std::move(nick),
                               "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.",
          true, resource);
372 373
          if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
            this->remove_resource_from_server(iid.get_server(), resource);
374 375
        }
    }
376
}
louiz’'s avatar
louiz’ committed
377

louiz’'s avatar
louiz’ committed
378 379
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick)
{
louiz’'s avatar
louiz’ committed
380
  IrcClient* irc = this->get_irc_client(iid.get_server());
381
  irc->send_nick_command(new_nick);
louiz’'s avatar
louiz’ committed
382 383
}

384 385 386 387 388 389
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
                                           const std::string& to_jid)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());

  irc->send_list_command();
390

391
  std::vector<ListElement> list;
392

393 394 395
  irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname,
                                                           const IrcMessage& message) mutable -> bool
    {
396 397
      if (irc_hostname != iid.get_server())
        return false;
398 399 400 401 402 403
      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];
404
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id,
405
                                        "wait", "service-unavailable", text, false);
406 407 408 409 410 411 412 413 414 415 416
          return true;
        }
      else if (message.command == "322" || message.command == "RPL_LIST")
        { // Add element to list
          if (message.arguments.size() == 4)
            list.emplace_back(message.arguments[1], 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
417
          this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list);
418 419 420 421 422 423 424
          return true;
        }
      return false;
    };
  this->add_waiting_irc(std::move(cb));
}

425 426
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)
427
{
louiz’'s avatar
louiz’ committed
428
  IrcClient* irc = this->get_irc_client(iid.get_server());
429 430 431 432

  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
433
    {
434 435 436 437 438 439 440
      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())
441
            return false;
442
          this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid));
443 444 445 446 447 448 449 450 451
        }
      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];
452
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found",
453 454 455 456 457 458 459 460 461 462
                                        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];
463
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed",
464 465 466 467 468
                                        error_message, false);
        }
      return true;
    };
  this->add_waiting_irc(std::move(cb));
469 470
}

471 472
void Bridge::set_channel_topic(const Iid& iid, const std::string& subject)
{
louiz’'s avatar
louiz’ committed
473
  IrcClient* irc = this->get_irc_client(iid.get_server());
474
  irc->send_topic_command(iid.get_local(), subject);
475 476
}

louiz’'s avatar
louiz’ committed
477 478 479 480 481 482 483
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);

  this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE");
}

484 485
void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id)
{
486
  this->send_private_message(iid, "\01PING "s + utils::revstr(id) + "\01", "NOTICE");
487 488 489 490 491 492
}

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

496
  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
497
    {
louiz’'s avatar
louiz’ committed
498
      if (irc_hostname != hostname || message.arguments.size() < 2)
499 500 501
        return false;
      IrcUser user(message.prefix);
      const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
502 503
      if (message.command == "NOTICE" && utils::tolower(user.nick) == nick
          && body.substr(0, 6) == "\01PING ")
504
        {
505
          const std::string id = body.substr(6, body.size() - 7);
506 507
          if (id != iq_id)
            return false;
508
          this->xmpp.send_iq_result_full_jid(iq_id, to_jid, from_jid);
509 510
          return true;
        }
louiz’'s avatar
louiz’ committed
511
      if (message.command == "401" && message.arguments[1] == nick)
512 513 514 515
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
516
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
517 518 519 520 521 522 523 524 525
                                        error_message, true);
          return true;
        }

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

526 527 528 529 530 531 532 533
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)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
  if (!chan->joined)
    {
534
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
535 536 537 538 539
                                    "", true);
      return;
    }
  if (chan->get_self()->nick != nick && !chan->find_user(nick))
    {
540
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
541 542 543 544 545 546 547 548
                                    "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);
}

549 550 551 552
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);
553
  if (irc_hostname.empty() || this->find_irc_client(irc_hostname))
554
    this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
555
  else
556
    this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
557 558 559
                                  "", true);
}

560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
void Bridge::send_irc_invitation(const Iid& iid, const std::string to)
{
  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);
}

576 577 578 579
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)
{
580
  Iid iid(target, irc_hostname, Iid::Type::User);
581 582 583
  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
584 585
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool
    {
586 587 588 589 590 591 592 593
      if (irc_hostname != hostname)
        return false;
      IrcUser user(message.prefix);
      if (message.command == "NOTICE" && user.nick == target &&
          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);
594
          this->xmpp.send_version(iq_id, to_jid, from_jid, version);
595 596 597 598 599 600 601 602
          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];
603
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
604 605 606 607
                                        error_message, true);
          return true;
        }
      return false;
608 609
    };
  this->add_waiting_irc(std::move(cb));
610 611
}

louiz’'s avatar
louiz’ committed
612
void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc)
louiz’'s avatar
louiz’ committed
613
{
614
  const auto encoding = in_encoding_for(*this, iid);
louiz’'s avatar
louiz’ committed
615
  if (muc)
616
    {
617
#ifdef USE_DATABASE
618
      const auto xmpp_body = this->make_xmpp_body(body, encoding);
619 620 621
      if (!nick.empty())
        Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
                                    std::get<0>(xmpp_body), nick);
622
#endif
623
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
624 625 626
        {
          this->xmpp.send_muc_message(std::to_string(iid), nick,
                                      this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource);
627

628 629
        }
    }
louiz’'s avatar
louiz’ committed
630
  else
631 632
    {
      std::string target = std::to_string(iid);
633
      const auto it = this->preferred_user_from.find(iid.get_local());
634 635
      if (it != this->preferred_user_from.end())
        {
636
          const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
637 638 639
          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),
                                    this->user_jid + "/" + resource, "chat", true);
640
        }
641
      else
642
        {
643 644 645
          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),
                                    this->user_jid + "/" + resource, "chat", false);
646
        }
647
    }
louiz’'s avatar
louiz’ committed
648 649
}

650 651 652
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)
653
{
654
  this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
655 656
}

657
void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource)
louiz’'s avatar
louiz’ committed
658
{
659 660 661 662 663 664 665
  if (!resource.empty())
    this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource,
                              self);
  else
    for (const auto& res: this->resources_in_chan[iid.to_tuple()])
      this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + res,
                                self);
666
  IrcClient* irc = this->find_irc_client(iid.get_server());
667 668
  if (irc && irc->number_of_joined_channels() == 0)
    irc->send_quit_command("");
louiz’'s avatar
louiz’ committed
669 670
}

671 672 673 674 675
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
676
{
677 678 679 680
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

681
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
682 683
    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
684 685
}

686 687
void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg)
{
louiz’'s avatar
louiz’ committed
688 689
  std::string body;
  if (!author.empty())
690 691
    {
      IrcUser user(author);
louiz’'s avatar
louiz’ committed
692
      body = "\u000303"s + user.nick + (user.host.empty()?
louiz’'s avatar
louiz’ committed
693 694
                                        "\u0003: ":
                                        (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg;
695
    }
louiz’'s avatar
louiz’ committed
696 697
  else
    body = msg;
698

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

706 707 708 709 710 711 712 713 714 715
void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
                            const IrcUser* user, const char user_mode, const bool self)
{
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    this->send_user_join(hostname, chan_name, user, user_mode, self, resource);
}

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)
716
{
717 718 719 720
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

721 722 723 724
  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,
725
                            affiliation, role, this->user_jid + "/" + resource, self);
726 727
}

728
void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who)
729
{
730 731
  for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
    {
732
      this->send_topic(hostname, chan_name, topic, who, resource);
733
    }
734
}
louiz’'s avatar
louiz’ committed
735

736 737 738 739
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)
{
740 741
  std::string encoded_chan_name(chan_name);
  xep0106::encode(encoded_chan_name);
742
  const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel});
743
  this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server(
744 745 746 747
      "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);

}

748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
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);
}

void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource)
{
#ifdef USE_DATABASE
  const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
  const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value());
  for (const auto& line: lines)
    {
      const auto seconds = line.date.value().timeStamp();
      this->xmpp.send_history_message(chan_name + "%" + hostname, line.nick.value(), line.body.value(),
                                      this->user_jid + "/" + resource, seconds);
    }
#endif
}

louiz’'s avatar
louiz’ committed
768 769
std::string Bridge::get_own_nick(const Iid& iid)
{
770
  IrcClient* irc = this->find_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
771 772 773 774
  if (irc)
    return irc->get_own_nick();
  return "";
}
775

776
size_t Bridge::active_clients() const
louiz’'s avatar
louiz’ committed
777 778 779 780
{
  return this->irc_clients.size();
}

781 782
void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author)
{
783
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
784
    this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource);
785
}
786 787 788

void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
{
789
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
790
    this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", "");
791
}
792 793 794 795 796

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

  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
799
  for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
800
    this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource);
801
}
802 803 804

void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
{
805 806
  const auto resources = this->resources_in_server[hostname];
  if (resources.begin() != resources.end())
807
    this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin());
808
}
809

810 811 812 813 814 815
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
  // the same that the request iq, but we also need to get it back easily
  // (revstr again)
816 817 818
  // Forward to the first resource (arbitrary, based on the “order” of the std::set) only
  const auto resources = this->resources_in_server[hostname];
  if (resources.begin() != resources.end())
819
    this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id));
820 821
}

louiz’'s avatar
louiz’ committed
822 823 824 825 826 827
void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author)
{
  for (const auto& resource: this->resources_in_server[iid.get_server()])
    this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author);
}

828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)
{
  auto it = this->preferred_user_from.find(nick);
  if (it == this->preferred_user_from.end())
    this->preferred_user_from.emplace(nick, full_jid);
  else
    this->preferred_user_from[nick] = full_jid;
}

void Bridge::remove_preferred_from_jid(const std::string& nick)
{
  auto it = this->preferred_user_from.find(nick);
  if (it != this->preferred_user_from.end())
    this->preferred_user_from.erase(it);
}
843

844 845 846 847
void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_name)
{
  for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();)
    {
848
      Iid iid(Jid(it->second).local, {});
849 850 851 852 853 854 855
      if (iid.get_local() == channel_name)
        it = this->preferred_user_from.erase(it);
      else
        ++it;
    }
}

856
void Bridge::add_waiting_irc(irc_responder_callback_t&& callback)
857
{
858
  this->waiting_irc.emplace_back(std::move(callback));
859 860
}

861
void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message)
862
{
863 864
  auto it = this->waiting_irc.begin();
  while (it != this->waiting_irc.end())
865 866
    {
      if ((*it)(irc_hostname, message) == true)
867
        it = this->waiting_irc.erase(it);
868 869 870 871
      else
        ++it;
    }
}
872 873 874 875 876

std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients()
{
  return this->irc_clients;
}
877

878 879 880 881 882 883 884 885
std::set<char> Bridge::get_chantypes(const std::string& hostname) const
{
  IrcClient* irc = this->find_irc_client(hostname);
  if (!irc)
    return {'#', '&'};
  return irc->get_chantypes();
}

886
void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
887 888 889 890 891 892 893 894
{
  auto it = this->resources_in_chan.find(channel);
  if (it == this->resources_in_chan.end())
    this->resources_in_chan[channel] = {resource};
  else
    it->second.insert(resource);
}

895
void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource)
896 897 898 899 900 901 902 903 904 905
{
  auto it = this->resources_in_chan.find(channel);
  if (it != this->resources_in_chan.end())
    {
      it->second.erase(resource);
      if (it->second.empty())
        this->resources_in_chan.erase(it);
    }
}

906
bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const
907 908 909 910 911 912 913
{
  auto it = this->resources_in_chan.find(channel);
  if (it != this->resources_in_chan.end())
    if (it->second.count(resource) == 1)
      return true;
  return false;
}
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943

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

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

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

944 945 946 947 948 949 950 951
std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel_key) const
{
  auto it = this->resources_in_chan.find(channel_key);
  if (it == this->resources_in_chan.end())
    return 0;
  return it->second.size();
}

952 953 954 955 956 957 958 959 960 961 962
std::size_t Bridge::number_of_channels_the_resource_is_in(const std::string& irc_hostname, const std::string& resource) const
{
  std::size_t res = 0;
  for (auto pair: this->resources_in_chan)
    {
      if (std::get<0>(pair.first) == irc_hostname && pair.second.count(resource) != 0)
        res++;
    }
  return res;
}

963 964 965 966
void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* channel = irc->get_channel(iid.get_local());
967 968
  const auto self = channel->get_self();

969 970 971
  // Send the occupant list
  for (const auto& user: channel->get_users())
    {
972 973 974
      if (user->nick != self->nick)
        {
          log_debug(user->nick);
975
          this->send_user_join(iid.get_server(), iid.get_encoded_local(),
976 977 978
                               user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()),
                               false, resource);
        }
979
    }
980
  this->send_user_join(iid.get_server(), iid.get_encoded_local(),
981 982
                       self, self->get_most_significant_mode(irc->get_sorted_user_modes()),
                       true, resource);
983
  this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource);
984
}