biboumi_component.cpp 27.7 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
#include <uuid/uuid.h>
24 25 26 27 28

#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 60 61 62 63
  this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false});
  this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false});
  this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true});
  this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false});
  this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true});
64

65 66
#ifdef USE_DATABASE
  AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false);
67
  AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false);
68

69
  if (!Config::get("fixed_irc_server", "").empty())
70
    this->adhoc_commands_handler.add_command("configure", configure_server_command);
71
  else
72
    this->adhoc_commands_handler.add_command("configure", configure_global_command);
73

74 75
  this->irc_server_adhoc_commands_handler.add_command("configure", configure_server_command);
  this->irc_channel_adhoc_commands_handler.add_command("configure", {{&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false});
76
#endif
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
}

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)
{
102
  std::string from_str = stanza.get_tag("from");
103 104 105 106 107
  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
108
  if (from_str.empty())
109 110 111 112 113 114
    {
      log_warning("Received an invalid presence stanza: tag 'from' is missing.");
      return;
    }
  if (to_str.empty())
    {
115
      this->send_stanza_error("presence", from_str, this->served_hostname, id,
116 117 118 119
                              "modify", "bad-request", "Missing 'to' tag");
      return;
    }

120
  Bridge* bridge = this->get_user_bridge(from_str);
121
  Jid to(to_str);
122
  Jid from(from_str);
123
  Iid iid(to.local, bridge);
124 125 126 127 128 129 130 131 132 133

  // 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([&](){
134
      this->send_stanza_error("presence", from_str, to_str, id,
135 136 137
                              error_type, error_name, "");
        });

138
  try {
139
  if (iid.type == Iid::Type::Channel && !iid.get_server().empty())
140 141 142 143 144 145 146
    { // 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);
147 148
          const XmlNode* x = stanza.get_child("x", MUC_NS);
          const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
149 150
          bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
                                   from.resource);
151 152 153
        }
      else if (type == "unavailable")
        {
154
          const XmlNode* status = stanza.get_child("status", COMPONENT_NS);
155
          bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource);
156 157 158 159
        }
    }
  else
    {
louiz’'s avatar
louiz’ committed
160
      // A user wants to join an invalid IRC channel, return a presence error to him/her
161
      if (type.empty())
162
        this->send_invalid_room_error(to.local, to.resource, from_str);
163
    }
164 165 166
  }
  catch (const IRCNotConnected& ex)
    {
167
      this->send_stanza_error("presence", from_str, to_str, id,
168 169 170 171
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);
    }
172 173 174 175 176
  stanza_error.disable();
}

void BiboumiComponent::handle_message(const Stanza& stanza)
{
177
  std::string from_str = stanza.get_tag("from");
178 179 180 181
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

182
  if (from_str.empty())
183 184 185
    return;
  if (type.empty())
    type = "normal";
186 187
  Bridge* bridge = this->get_user_bridge(from_str);
  Jid from(from_str);
188
  Jid to(to_str);
189
  Iid iid(to.local, bridge);
190 191 192 193

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
194
      this->send_stanza_error("message", from_str, to_str, id,
195 196
                              error_type, error_name, "");
    });
197
  const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
198 199

  try {                         // catch IRCNotConnected exceptions
200
  if (type == "groupchat" && iid.type == Iid::Type::Channel)
201 202 203 204 205
    {
      if (body && !body->get_inner().empty())
        {
          bridge->send_channel_message(iid, body->get_inner());
        }
206
      const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
207 208 209 210 211 212 213
      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
214
      // them, we purge (we disconnect that resource from all the IRC servers)
215 216 217 218 219 220 221 222 223 224
      // 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)
225
        bridge->remove_resource(from.resource, "Error from remote client");
226 227 228 229 230 231
    }
  else if (type == "chat")
    {
      if (body && !body->get_inner().empty())
        {
          // a message for nick!server
232
          if (iid.type == Iid::Type::User && !iid.get_local().empty())
233 234 235 236
            {
              bridge->send_private_message(iid, body->get_inner());
              bridge->remove_preferred_from_jid(iid.get_local());
            }
237
          else if (iid.type != Iid::Type::User && !to.resource.empty())
238 239 240
            { // a message for chan%server@biboumi/Nick or
              // server@biboumi/Nick
              // Convert that into a message to nick!server
241
              Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
242 243 244
              bridge->send_private_message(user_iid, body->get_inner());
              bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
            }
245
          else if (iid.type == Iid::Type::Server)
louiz’'s avatar
louiz’ committed
246 247 248 249
            { // 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());
            }
250 251
        }
    }
252 253 254 255 256 257 258 259 260 261 262 263 264
  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);
              }
          }

    }
265
  else if (iid.type == Iid::Type::User)
266
    this->send_invalid_user_error(to.local, from_str);
267 268
  } catch (const IRCNotConnected& ex)
    {
269
      this->send_stanza_error("message", from_str, to_str, id,
270 271 272 273
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);
    }
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
  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");

296 297
  if (from.empty()) {
    log_warning("Received an iq without a 'from'. Ignoring.");
298
    return;
299
  }
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
  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, "");
    });
318
  try {
319 320
  if (type == "set")
    {
321
      const XmlNode* query;
322 323 324 325 326 327 328 329 330 331
      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())
                {
332
                  Iid iid(to.local, {});
333 334 335
                  if (role == "none")
                    {               // This is a kick
                      std::string reason;
336
                      const XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
337 338 339 340 341 342 343 344 345 346 347 348 349 350
                      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;
351
          response["from"] = to_str;
352
          response["id"] = id;
353 354 355

          // Depending on the 'to' jid in the request, we use one adhoc
          // command handler or an other
356
          Iid iid(to.local, {});
357
          AdhocCommandsHandler* adhoc_handler;
358
          if (to.local.empty())
359
            adhoc_handler = &this->adhoc_commands_handler;
360 361 362 363 364 365 366
          else
          {
            if (iid.type == Iid::Type::Server)
              adhoc_handler = &this->irc_server_adhoc_commands_handler;
            else
              adhoc_handler = &this->irc_channel_adhoc_commands_handler;
          }
367 368 369
          // 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);
370 371 372 373 374 375 376 377
          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();
        }
378 379 380 381 382 383 384
#ifdef USE_DATABASE
      else if ((query = stanza.get_child("query", MAM_NS)))
        {
          if (this->handle_mam_request(stanza))
            stanza_error.disable();
        }
#endif
385 386 387
    }
  else if (type == "get")
    {
388
      const XmlNode* query;
389 390
      if ((query = stanza.get_child("query", DISCO_INFO_NS)))
        { // Disco info
391 392
          Iid iid(to.local, {});
          const std::string node = query->get_tag("node");
393 394 395 396 397 398 399 400 401
          if (to_str == this->served_hostname)
            {
              if (node.empty())
                {
                  // On the gateway itself
                  this->send_self_disco_info(id, from);
                  stanza_error.disable();
                }
            }
402 403 404 405 406 407 408 409
          else if (iid.type == Iid::Type::Server)
            {
                if (node.empty())
                {
                    this->send_irc_server_disco_info(id, from, to_str);
                    stanza_error.disable();
                }
            }
410 411 412
        }
      else if ((query = stanza.get_child("query", VERSION_NS)))
        {
413 414
          Iid iid(to.local, bridge);
          if (iid.type != Iid::Type::Server && !to.resource.empty())
415 416 417
            {
              // Get the IRC user version
              std::string target;
418
              if (iid.type == Iid::Type::User)
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
                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)))
        {
434
          Iid iid(to.local, bridge);
435 436 437
          const std::string node = query->get_tag("node");
          if (node == ADHOC_NS)
            {
438
              Jid from_jid(from);
439 440 441 442
              if (to.local.empty())
                {               // Get biboumi's adhoc commands
                  this->send_adhoc_commands_list(id, from, this->served_hostname,
                                                 (Config::get("admin", "") ==
443
                                                  from_jid.bare()),
444 445 446
                                                 this->adhoc_commands_handler);
                  stanza_error.disable();
                }
447
              else if (iid.type == Iid::Type::Server)
448 449 450
                {               // Get the server's adhoc commands
                  this->send_adhoc_commands_list(id, from, to_str,
                                                 (Config::get("admin", "") ==
451
                                                  from_jid.bare()),
452 453 454
                                                 this->irc_server_adhoc_commands_handler);
                  stanza_error.disable();
                }
455
              else if (iid.type == Iid::Type::Channel)
456 457 458 459 460 461 462
                {               // 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();
                }
463
            }
464
          else if (node.empty() && iid.type == Iid::Type::Server)
465 466 467 468 469 470 471
            { // 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)))
        {
472 473
          Iid iid(to.local, bridge);
          if (iid.type == Iid::Type::User)
474 475 476 477
            { // 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);
            }
478
          else if (iid.type == Iid::Type::Channel && !to.resource.empty())
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
            { // 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();
494
      const XmlNode* query;
495 496
      if ((query = stanza.get_child("query", VERSION_NS)))
        {
497 498 499
          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);
500 501 502 503 504 505 506 507 508
          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();
509
          const Iid iid(to.local, bridge);
510 511 512 513 514 515 516 517 518 519 520 521
          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);
            }
        }
    }
522 523 524 525 526 527 528 529 530 531
  }
  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;
    }
532 533 534 535
  error_type = "cancel";
  error_name = "feature-not-implemented";
}

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

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

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

louiz’'s avatar
louiz’ committed
653
std::vector<Bridge*> BiboumiComponent::get_bridges() const
654
{
louiz’'s avatar
louiz’ committed
655
  std::vector<Bridge*> res;
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
  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));
675
  for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
676 677 678 679 680 681 682 683 684
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      query.add_child(std::move(feature));
    }
  iq.add_child(std::move(query));
  this->send_stanza(iq);
}

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
{
  Jid from(jid_from);
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = jid_from;
  XmlNode query("query");
  query["xmlns"] = DISCO_INFO_NS;
  XmlNode identity("identity");
  identity["category"] = "conference";
  identity["type"] = "irc";
  identity["name"] = "IRC server "s + from.local + " over Biboumi";
  query.add_child(std::move(identity));
  for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS})
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      query.add_child(std::move(feature));
    }
  iq.add_child(std::move(query));
  this->send_stanza(iq);
}

710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
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,
725 726
                                         const std::string& jid_to,
                                         const std::string& id)
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
{
  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
747
        bridge->send_irc_ping_result({from, bridge}, id);
748 749 750 751 752
    };
  this->waiting_iq[id] = result_cb;
}

void BiboumiComponent::send_iq_room_list_result(const std::string& id,
753
                                             const std::string& to_jid,
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
                                             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
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788

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