biboumi_component.cpp 27 KB
Newer Older
1 2 3 4 5 6 7
#include <xmpp/biboumi_component.hpp>

#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>
#include <xmpp/adhoc_command.hpp>
8
#include <xmpp/biboumi_adhoc_commands.hpp>
9 10 11
#include <bridge/list_element.hpp>
#include <config/config.hpp>
#include <utils/sha1.hpp>
12 13
#include <utils/time.hpp>
#include <xmpp/jid.hpp>
14 15 16 17 18 19 20

#include <stdexcept>
#include <iostream>

#include <stdio.h>

#include <louloulibs.h>
21
#include <biboumi.h>
22 23 24 25 26 27 28

#include <uuid.h>

#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif

29 30
#include <database/database.hpp>

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
using namespace std::string_literals;

static std::set<std::string> kickable_errors{
    "gone",
    "internal-server-error",
    "item-not-found",
    "jid-malformed",
    "recipient-unavailable",
    "redirect",
    "remote-server-not-found",
    "remote-server-timeout",
    "service-unavailable",
    "malformed-error"
    };


BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
48
  XmppComponent(poller, hostname, secret),
49 50
  irc_server_adhoc_commands_handler(*this),
  irc_channel_adhoc_commands_handler(*this)
51 52 53 54 55 56 57 58
{
  this->stanza_handlers.emplace("presence",
                                std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1));
  this->stanza_handlers.emplace("message",
                                std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1));
  this->stanza_handlers.emplace("iq",
                                std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1));

59
  this->adhoc_commands_handler.get_commands() = {
60 61
    {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
    {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)},
62 63
    {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true)},
    {"disconnect-from-irc-servers", AdhocCommand({&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false)},
64 65
    {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
  };
66

67 68
#ifdef USE_DATABASE
  AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false);
69
  AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false);
70 71
  if (!Config::get("fixed_irc_server", "").empty())
    this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure",
72 73 74 75
                                                                       configure_server_command));
  else
    this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure",
                                                                       configure_global_command));
76 77
#endif

78 79
  this->irc_server_adhoc_commands_handler.get_commands() = {
#ifdef USE_DATABASE
80
    {"configure", configure_server_command},
81 82
#endif
  };
83
  this->irc_channel_adhoc_commands_handler.get_commands() = {
84
#ifdef USE_DATABASE
85
    {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)},
86
#endif
87
  };
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
}

void BiboumiComponent::shutdown()
{
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
  {
    it->second->shutdown("Gateway shutdown");
  }
}

void BiboumiComponent::clean()
{
  auto it = this->bridges.begin();
  while (it != this->bridges.end())
  {
    it->second->clean();
    if (it->second->active_clients() == 0)
      it = this->bridges.erase(it);
    else
      ++it;
  }
}

void BiboumiComponent::handle_presence(const Stanza& stanza)
{
113
  std::string from_str = stanza.get_tag("from");
114 115 116 117 118
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

  // Check for mandatory tags
119
  if (from_str.empty())
120 121 122 123 124 125
    {
      log_warning("Received an invalid presence stanza: tag 'from' is missing.");
      return;
    }
  if (to_str.empty())
    {
126
      this->send_stanza_error("presence", from_str, this->served_hostname, id,
127 128 129 130
                              "modify", "bad-request", "Missing 'to' tag");
      return;
    }

131
  Bridge* bridge = this->get_user_bridge(from_str);
132
  Jid to(to_str);
133
  Jid from(from_str);
134
  Iid iid(to.local, bridge);
135 136 137 138 139 140 141 142 143 144

  // An error stanza is sent whenever we exit this function without
  // disabling this scopeguard.  If error_type and error_name are not
  // changed, the error signaled is internal-server-error. Change their
  // value to signal an other kind of error. For example
  // feature-not-implemented, etc.  Any non-error process should reach the
  // stanza_error.disable() call at the end of the function.
  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
145
      this->send_stanza_error("presence", from_str, to_str, id,
146 147 148
                              error_type, error_name, "");
        });

149
  try {
150
  if (iid.type == Iid::Type::Channel && !iid.get_server().empty())
151 152 153 154 155 156 157
    { // presence toward a MUC that corresponds to an irc channel, or a
      // dummy channel if iid.chan is empty
      if (type.empty())
        {
          const std::string own_nick = bridge->get_own_nick(iid);
          if (!own_nick.empty() && own_nick != to.resource)
            bridge->send_irc_nick_change(iid, to.resource);
158 159
          const XmlNode* x = stanza.get_child("x", MUC_NS);
          const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
160 161
          bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
                                   from.resource);
162 163 164
        }
      else if (type == "unavailable")
        {
165
          const XmlNode* status = stanza.get_child("status", COMPONENT_NS);
166
          bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource);
167 168 169 170 171 172
        }
    }
  else
    {
      // An user wants to join an invalid IRC channel, return a presence error to him
      if (type.empty())
173
        this->send_invalid_room_error(to.local, to.resource, from_str);
174
    }
175 176 177
  }
  catch (const IRCNotConnected& ex)
    {
178
      this->send_stanza_error("presence", from_str, to_str, id,
179 180 181 182
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);
    }
183 184 185 186 187
  stanza_error.disable();
}

void BiboumiComponent::handle_message(const Stanza& stanza)
{
188
  std::string from_str = stanza.get_tag("from");
189 190 191 192
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

193
  if (from_str.empty())
194 195 196
    return;
  if (type.empty())
    type = "normal";
197 198
  Bridge* bridge = this->get_user_bridge(from_str);
  Jid from(from_str);
199
  Jid to(to_str);
200
  Iid iid(to.local, bridge);
201 202 203 204

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
205
      this->send_stanza_error("message", from_str, to_str, id,
206 207
                              error_type, error_name, "");
    });
208
  const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
209 210

  try {                         // catch IRCNotConnected exceptions
211
  if (type == "groupchat" && iid.type == Iid::Type::Channel)
212 213 214 215 216
    {
      if (body && !body->get_inner().empty())
        {
          bridge->send_channel_message(iid, body->get_inner());
        }
217
      const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
218 219 220 221 222 223 224
      if (subject)
        bridge->set_channel_topic(iid, subject->get_inner());
    }
  else if (type == "error")
    {
      const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
      // Only a set of errors are considered “fatal”. If we encounter one of
225
      // them, we purge (we disconnect that resource from all the IRC servers)
226 227 228 229 230 231 232 233 234 235
      // We consider this to be true, unless the error condition is
      // specified and is not in the kickable_errors set
      bool kickable_error = true;
      if (error && error->has_children())
        {
          const XmlNode* condition = error->get_last_child();
          if (kickable_errors.find(condition->get_name()) == kickable_errors.end())
            kickable_error = false;
        }
      if (kickable_error)
236
        bridge->remove_resource(from.resource, "Error from remote client");
237 238 239 240 241 242
    }
  else if (type == "chat")
    {
      if (body && !body->get_inner().empty())
        {
          // a message for nick!server
243
          if (iid.type == Iid::Type::User && !iid.get_local().empty())
244 245 246 247
            {
              bridge->send_private_message(iid, body->get_inner());
              bridge->remove_preferred_from_jid(iid.get_local());
            }
248
          else if (iid.type != Iid::Type::User && !to.resource.empty())
249 250 251
            { // a message for chan%server@biboumi/Nick or
              // server@biboumi/Nick
              // Convert that into a message to nick!server
252
              Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
253 254 255
              bridge->send_private_message(user_iid, body->get_inner());
              bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
            }
256
          else if (iid.type == Iid::Type::Server)
louiz’'s avatar
louiz’ committed
257 258 259 260
            { // Message sent to the server JID
              // Convert the message body into a raw IRC message
              bridge->send_raw_message(iid.get_server(), body->get_inner());
            }
261 262
        }
    }
263 264 265 266 267 268 269 270 271 272 273 274 275
  else if (type == "normal" && iid.type == Iid::Type::Channel)
    {
      if (const XmlNode* x = stanza.get_child("x", MUC_USER_NS))
        if (const XmlNode* invite = x->get_child("invite", MUC_USER_NS))
          {
            const auto invite_to = invite->get_tag("to");
            if (!invite_to.empty())
              {
                bridge->send_irc_invitation(iid, invite_to);
              }
          }

    }
276
  else if (iid.type == Iid::Type::User)
277
    this->send_invalid_user_error(to.local, from_str);
278 279
  } catch (const IRCNotConnected& ex)
    {
280
      this->send_stanza_error("message", from_str, to_str, id,
281 282 283 284
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);
    }
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
  stanza_error.disable();
}

// We MUST return an iq, whatever happens, except if the type is
// "result".
// To do this, we use a scopeguard. If an exception is raised somewhere, an
// iq of type error "internal-server-error" is sent. If we handle the
// request properly (by calling a function that registers an iq to be sent
// later, or that directly sends an iq), we disable the ScopeGuard. If we
// reach the end of the function without having disabled the scopeguard, we
// send a "feature-not-implemented" iq as a result.  If an other kind of
// error is found (for example the feature is implemented in biboumi, but
// the request is missing some attribute) we can just change the values of
// error_type and error_name and return from the function (without disabling
// the scopeguard); an iq error will be sent
void BiboumiComponent::handle_iq(const Stanza& stanza)
{
  std::string id = stanza.get_tag("id");
  std::string from = stanza.get_tag("from");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

307 308
  if (from.empty()) {
    log_warning("Received an iq without a 'from'. Ignoring.");
309
    return;
310
  }
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
  if (id.empty() || to_str.empty() || type.empty())
    {
      this->send_stanza_error("iq", from, this->served_hostname, id,
                              "modify", "bad-request", "");
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);

  // These two values will be used in the error iq sent if we don't disable
  // the scopeguard.
  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
      this->send_stanza_error("iq", from, to_str, id,
                              error_type, error_name, "");
    });
329
  try {
330 331
  if (type == "set")
    {
332
      const XmlNode* query;
333 334 335 336 337 338 339 340 341 342
      if ((query = stanza.get_child("query", MUC_ADMIN_NS)))
        {
          const XmlNode* child = query->get_child("item", MUC_ADMIN_NS);
          if (child)
            {
              std::string nick = child->get_tag("nick");
              std::string role = child->get_tag("role");
              std::string affiliation = child->get_tag("affiliation");
              if (!nick.empty())
                {
343
                  Iid iid(to.local, {});
344 345 346
                  if (role == "none")
                    {               // This is a kick
                      std::string reason;
347
                      const XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
348 349 350 351 352 353 354 355 356 357 358 359 360 361
                      if (reason_el)
                        reason = reason_el->get_inner();
                      bridge->send_irc_kick(iid, nick, reason, id, from);
                    }
                  else
                    bridge->forward_affiliation_role_change(iid, nick, affiliation, role);
                  stanza_error.disable();
                }
            }
        }
      else if ((query = stanza.get_child("command", ADHOC_NS)))
        {
          Stanza response("iq");
          response["to"] = from;
362
          response["from"] = to_str;
363
          response["id"] = id;
364 365 366

          // Depending on the 'to' jid in the request, we use one adhoc
          // command handler or an other
367
          Iid iid(to.local, {});
368
          AdhocCommandsHandler* adhoc_handler;
369
          if (to.local.empty())
370
            adhoc_handler = &this->adhoc_commands_handler;
371 372 373 374 375 376 377
          else
          {
            if (iid.type == Iid::Type::Server)
              adhoc_handler = &this->irc_server_adhoc_commands_handler;
            else
              adhoc_handler = &this->irc_channel_adhoc_commands_handler;
          }
378 379 380
          // Execute the command, if any, and get a result XmlNode that we
          // insert in our response
          XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query);
381 382 383 384 385 386 387 388
          if (inner_node.get_child("error", ADHOC_NS))
            response["type"] = "error";
          else
            response["type"] = "result";
          response.add_child(std::move(inner_node));
          this->send_stanza(response);
          stanza_error.disable();
        }
389 390 391 392 393 394 395
#ifdef USE_DATABASE
      else if ((query = stanza.get_child("query", MAM_NS)))
        {
          if (this->handle_mam_request(stanza))
            stanza_error.disable();
        }
#endif
396 397 398
    }
  else if (type == "get")
    {
399
      const XmlNode* query;
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
      if ((query = stanza.get_child("query", DISCO_INFO_NS)))
        { // Disco info
          if (to_str == this->served_hostname)
            {
              const std::string node = query->get_tag("node");
              if (node.empty())
                {
                  // On the gateway itself
                  this->send_self_disco_info(id, from);
                  stanza_error.disable();
                }
            }
        }
      else if ((query = stanza.get_child("query", VERSION_NS)))
        {
415 416
          Iid iid(to.local, bridge);
          if (iid.type != Iid::Type::Server && !to.resource.empty())
417 418 419
            {
              // Get the IRC user version
              std::string target;
420
              if (iid.type == Iid::Type::User)
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
                target = iid.get_local();
              else
                target = to.resource;
              bridge->send_irc_version_request(iid.get_server(), target, id,
                                               from, to_str);
            }
          else
            {
              // On the gateway itself or on a channel
              this->send_version(id, from, to_str);
            }
          stanza_error.disable();
        }
      else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
        {
436
          Iid iid(to.local, bridge);
437 438 439
          const std::string node = query->get_tag("node");
          if (node == ADHOC_NS)
            {
440
              Jid from_jid(from);
441 442 443 444
              if (to.local.empty())
                {               // Get biboumi's adhoc commands
                  this->send_adhoc_commands_list(id, from, this->served_hostname,
                                                 (Config::get("admin", "") ==
445
                                                  from_jid.bare()),
446 447 448
                                                 this->adhoc_commands_handler);
                  stanza_error.disable();
                }
449
              else if (iid.type == Iid::Type::Server)
450 451 452
                {               // Get the server's adhoc commands
                  this->send_adhoc_commands_list(id, from, to_str,
                                                 (Config::get("admin", "") ==
453
                                                  from_jid.bare()),
454 455 456
                                                 this->irc_server_adhoc_commands_handler);
                  stanza_error.disable();
                }
457
              else if (iid.type == Iid::Type::Channel)
458 459 460 461 462 463 464
                {               // Get the channel's adhoc commands
                  this->send_adhoc_commands_list(id, from, to_str,
                                                 (Config::get("admin", "") ==
                                                  from_jid.bare()),
                                                 this->irc_channel_adhoc_commands_handler);
                  stanza_error.disable();
                }
465
            }
466
          else if (node.empty() && iid.type == Iid::Type::Server)
467 468 469 470 471 472 473
            { // Disco on an IRC server: get the list of channels
              bridge->send_irc_channel_list_request(iid, id, from);
              stanza_error.disable();
            }
        }
      else if ((query = stanza.get_child("ping", PING_NS)))
        {
474 475
          Iid iid(to.local, bridge);
          if (iid.type == Iid::Type::User)
476 477 478 479
            { // Ping any user (no check on the nick done ourself)
              bridge->send_irc_user_ping_request(iid.get_server(),
                                                 iid.get_local(), id, from, to_str);
            }
480
          else if (iid.type == Iid::Type::Channel && !to.resource.empty())
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
            { // Ping a room participant (we check if the nick is in the room)
              bridge->send_irc_participant_ping_request(iid,
                                                        to.resource, id, from, to_str);
            }
          else
            { // Ping a channel, a server or the gateway itself
              bridge->on_gateway_ping(iid.get_server(),
                                     id, from, to_str);
            }
          stanza_error.disable();
        }
    }
  else if (type == "result")
    {
      stanza_error.disable();
496
      const XmlNode* query;
497 498
      if ((query = stanza.get_child("query", VERSION_NS)))
        {
499 500 501
          const XmlNode* name_node = query->get_child("name", VERSION_NS);
          const XmlNode* version_node = query->get_child("version", VERSION_NS);
          const XmlNode* os_node = query->get_child("os", VERSION_NS);
502 503 504 505 506 507 508 509 510
          std::string name;
          std::string version;
          std::string os;
          if (name_node)
            name = name_node->get_inner() + " (through the biboumi gateway)";
          if (version_node)
            version = version_node->get_inner();
          if (os_node)
            os = os_node->get_inner();
511
          const Iid iid(to.local, bridge);
512 513 514 515 516 517 518 519 520 521 522 523
          bridge->send_xmpp_version_to_irc(iid, name, version, os);
        }
      else
        {
          const auto it = this->waiting_iq.find(id);
          if (it != this->waiting_iq.end())
            {
              it->second(bridge, stanza);
              this->waiting_iq.erase(it);
            }
        }
    }
524 525 526 527 528 529 530 531 532 533
  }
  catch (const IRCNotConnected& ex)
    {
      this->send_stanza_error("iq", from, to_str, id,
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);
      stanza_error.disable();
      return;
    }
534 535 536 537
  error_type = "cancel";
  error_name = "feature-not-implemented";
}

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
#ifdef USE_DATABASE
bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
{
    std::string id = stanza.get_tag("id");
    Jid from(stanza.get_tag("from"));
    Jid to(stanza.get_tag("to"));

    const XmlNode* query = stanza.get_child("query", MAM_NS);
    std::string query_id;
    if (query)
      query_id = query->get_tag("queryid");

    Iid iid(to.local, {'#', '&'});
    if (iid.type == Iid::Type::Channel && to.resource.empty())
      {
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 579 580 581 582
        std::string start;
        std::string end;
        const XmlNode* x = query->get_child("x", DATAFORM_NS);
        if (x)
          {
            const XmlNode* value;
            const auto fields = x->get_children("field", DATAFORM_NS);
            for (const auto& field: fields)
              {
                if (field->get_tag("var") == "start")
                  {
                    value = field->get_child("value", DATAFORM_NS);
                    if (value)
                      start = value->get_inner();
                  }
                else if (field->get_tag("var") == "end")
                  {
                    value = field->get_child("value", DATAFORM_NS);
                    if (value)
                      end = value->get_inner();
                  }
              }
          }
        const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end);
        for (const db::MucLogLine& line: lines)
        {
          const auto queryid = query->get_tag("queryid");
          if (!line.nick.value().empty())
            this->send_archived_message(line, to.full(), from.full(), queryid);
        }
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        this->send_iq_result_full_jid(id, from.full(), to.full());
        return true;
      }
  return false;
}

void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
                                             const std::string& queryid)
{
    Stanza message("message");
    message["from"] = from;
    message["to"] = to;

    XmlNode result("result");
    result["xmlns"] = MAM_NS;
    result["queryid"] = queryid;
    result["id"] = log_line.uuid.value();

    XmlNode forwarded("forwarded");
    forwarded["xmlns"] = FORWARD_NS;

    XmlNode delay("delay");
    delay["xmlns"] = DELAY_NS;
    delay["stamp"] = utils::to_string(log_line.date.value().timeStamp());

    forwarded.add_child(std::move(delay));

    XmlNode submessage("message");
    submessage["xmlns"] = CLIENT_NS;
    submessage["from"] = from + "/" + log_line.nick.value();
    submessage["type"] = "groupchat";

    XmlNode body("body");
    body.set_inner(log_line.body.value());
    submessage.add_child(std::move(body));

    forwarded.add_child(std::move(submessage));
    result.add_child(std::move(forwarded));
    message.add_child(std::move(result));

    this->send_stanza(message);
}

#endif

628 629
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
{
630
  auto bare_jid = Jid{user_jid}.bare();
631 632
  try
    {
633
      return this->bridges.at(bare_jid).get();
634 635 636
    }
  catch (const std::out_of_range& exception)
    {
637 638
      this->bridges.emplace(bare_jid, std::make_unique<Bridge>(bare_jid, *this, this->poller));
      return this->bridges.at(bare_jid).get();
639 640 641
    }
}

642
Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid)
643
{
644
  auto bare_jid = Jid{full_jid}.bare();
645 646
  try
    {
647
      return this->bridges.at(bare_jid).get();
648 649 650 651 652 653 654
    }
  catch (const std::out_of_range& exception)
    {
      return nullptr;
    }
}

louiz’'s avatar
louiz’ committed
655
std::vector<Bridge*> BiboumiComponent::get_bridges() const
656
{
louiz’'s avatar
louiz’ committed
657
  std::vector<Bridge*> res;
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
    res.push_back(it->second.get());
  return res;
}

void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to)
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = this->served_hostname;
  XmlNode query("query");
  query["xmlns"] = DISCO_INFO_NS;
  XmlNode identity("identity");
  identity["category"] = "conference";
  identity["type"] = "irc";
  identity["name"] = "Biboumi XMPP-IRC gateway";
  query.add_child(std::move(identity));
677
  for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS})
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      query.add_child(std::move(feature));
    }
  iq.add_child(std::move(query));
  this->send_stanza(iq);
}

void BiboumiComponent::send_iq_version_request(const std::string& from,
                                            const std::string& jid_to)
{
  Stanza iq("iq");
  iq["type"] = "get";
  iq["id"] = "version_"s + this->next_id();
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = jid_to;
  XmlNode query("query");
  query["xmlns"] = VERSION_NS;
  iq.add_child(std::move(query));
  this->send_stanza(iq);
}

void BiboumiComponent::send_ping_request(const std::string& from,
702 703
                                         const std::string& jid_to,
                                         const std::string& id)
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
{
  Stanza iq("iq");
  iq["type"] = "get";
  iq["id"] = id;
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = jid_to;
  XmlNode ping("ping");
  ping["xmlns"] = PING_NS;
  iq.add_child(std::move(ping));
  this->send_stanza(iq);

  auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza)
    {
      Jid to(stanza.get_tag("to"));
      if (to.local != from)
        {
          log_error("Received a corresponding ping result, but the 'to' from "
                    "the response mismatches the 'from' of the request");
        }
      else
724
        bridge->send_irc_ping_result({from, bridge}, id);
725 726 727 728 729
    };
  this->waiting_iq[id] = result_cb;
}

void BiboumiComponent::send_iq_room_list_result(const std::string& id,
730
                                             const std::string& to_jid,
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
                                             const std::string& from,
                                             const std::vector<ListElement>& rooms_list)
{
  Stanza iq("iq");
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = to_jid;
  iq["id"] = id;
  iq["type"] = "result";
  XmlNode query("query");
  query["xmlns"] = DISCO_ITEMS_NS;
  for (const auto& room: rooms_list)
    {
      XmlNode item("item");
      item["jid"] = room.channel + "%" + from + "@" + this->served_hostname;
      query.add_child(std::move(item));
    }
  iq.add_child(std::move(query));
  this->send_stanza(iq);
}
louiz’'s avatar
louiz’ committed
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765

void BiboumiComponent::send_invitation(const std::string& room_target,
                                       const std::string& jid_to,
                                       const std::string& author_nick)
{
  Stanza message("message");
  message["from"] = room_target + "@" + this->served_hostname;
  message["to"] = jid_to;
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode invite("invite");
  invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick;
  x.add_child(std::move(invite));
  message.add_child(std::move(x));
  this->send_stanza(message);
}