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 619 620
      const auto xmpp_body = this->make_xmpp_body(body, encoding);
      Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
                                  std::get<0>(xmpp_body), nick);
621
#endif
622
      for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
623 624 625
        {
          this->xmpp.send_muc_message(std::to_string(iid), nick,
                                      this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource);
626

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

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

656
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
657
{
658 659 660 661 662 663 664
  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);
665
  IrcClient* irc = this->find_irc_client(iid.get_server());
666 667
  if (irc && irc->number_of_joined_channels() == 0)
    irc->send_quit_command("");
louiz’'s avatar
louiz’ committed
668 669
}

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

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

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

698
  const auto encoding = in_encoding_for(*this, {from, this});
699 700 701 702
  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");
    }
703 704
}

705 706 707 708 709 710 711 712 713 714
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)
715
{
716 717 718 719
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

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

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

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

}

747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
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
767 768
std::string Bridge::get_own_nick(const Iid& iid)
{
769
  IrcClient* irc = this->find_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
770 771 772 773
  if (irc)
    return irc->get_own_nick();
  return "";
}
774

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

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

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

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

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

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

809 810 811 812 813 814
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)
815 816 817
  // 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())
818
    this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id));
819 820
}

louiz’'s avatar
louiz’ committed
821 822 823 824 825 826
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);
}

827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
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);
}
842

843 844 845 846
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();)
    {
847
      Iid iid(Jid(it->second).local, {});
848 849 850 851 852 853 854
      if (iid.get_local() == channel_name)
        it = this->preferred_user_from.erase(it);
      else
        ++it;
    }
}

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

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

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

877 878 879 880 881 882 883 884
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();
}

885
void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
886 887 888 889 890 891 892 893
{
  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);
}

894
void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource)
895 896 897 898 899 900 901 902 903 904
{
  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);
    }
}

905
bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const
906 907 908 909 910 911 912
{
  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;
}
913 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

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

943 944 945 946 947 948 949 950
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();
}

951 952 953 954 955 956 957 958 959 960 961
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;
}

962 963 964 965
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());
966 967
  const auto self = channel->get_self();

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