xmpp_component.cpp 20.4 KB
Newer Older
1 2 3 4 5 6 7
#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>

#include <xmpp/xmpp_component.hpp>
#include <config/config.hpp>
8
#include <utils/system.hpp>
9
#include <utils/time.hpp>
10 11
#include <xmpp/auth.hpp>
#include <xmpp/jid.hpp>
12

louiz’'s avatar
louiz’ committed
13 14 15 16
#include <stdexcept>
#include <iostream>
#include <set>

17
#include <uuid/uuid.h>
18

louiz’'s avatar
louiz’ committed
19 20 21
#include <cstdlib>
#include <set>

louiz’'s avatar
louiz’ committed
22
#include <biboumi.h>
louiz’'s avatar
louiz’ committed
23 24 25 26
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
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"
    };

louiz’'s avatar
louiz’ committed
42
XmppComponent::XmppComponent(std::shared_ptr<Poller>& poller, std::string hostname, std::string secret):
43
  TCPClientSocketHandler(poller),
44 45
  ever_auth(false),
  first_connection_try(true),
louiz’'s avatar
louiz’ committed
46
  secret(std::move(secret)),
47 48
  authenticated(false),
  doc_open(false),
louiz’'s avatar
louiz’ committed
49
  served_hostname(std::move(hostname)),
50
  stanza_handlers{},
51
  adhoc_commands_handler(*this)
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
{
  this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this,
                                                  std::placeholders::_1));
  this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this,
                                                  std::placeholders::_1));
  this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this,
                                                  std::placeholders::_1));
  this->stanza_handlers.emplace("handshake",
                                std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1));
  this->stanza_handlers.emplace("error",
                                std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
}

void XmppComponent::start()
{
67
  this->connect(Config::get("xmpp_server_ip", "127.0.0.1"), Config::get("port", "5347"), false);
68 69 70 71 72 73 74 75 76 77
}

bool XmppComponent::is_document_open() const
{
  return this->doc_open;
}

void XmppComponent::send_stanza(const Stanza& stanza)
{
  std::string str = stanza.to_string();
78
  log_debug("XMPP SENDING: ", str);
79 80 81 82 83 84
  this->send_data(std::move(str));
}

void XmppComponent::on_connection_failed(const std::string& reason)
{
  this->first_connection_try = false;
85
  log_error("Failed to connect to the XMPP server: ", reason);
86 87 88 89 90 91 92 93 94
#ifdef SYSTEMD_FOUND
  sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data());
#endif
}

void XmppComponent::on_connected()
{
  log_info("connected to XMPP server");
  this->first_connection_try = true;
95 96
  auto data = "<stream:stream to='"s + this->served_hostname + \
    "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='" COMPONENT_NS "'>";
97
  log_debug("XMPP SENDING: ", data);
98
  this->send_data(std::move(data));
99 100 101 102 103 104 105 106 107
  this->doc_open = true;
  // We may have some pending data to send: this happens when we try to send
  // some data before we are actually connected.  We send that data right now, if any
  this->send_pending_data();
}

void XmppComponent::on_connection_close(const std::string& error)
{
  if (error.empty())
louiz’'s avatar
louiz’ committed
108
    log_info("XMPP server closed connection");
109
  else
110
    log_info("XMPP server closed connection: ", error);
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
}

void XmppComponent::parse_in_buffer(const size_t size)
{
  if (!this->in_buf.empty())
    { // This may happen if the parser could not allocate enough space for
      // us. We try to feed it the data that was read into our in_buf
      // instead. If this fails again we are in trouble.
      this->parser.feed(this->in_buf.data(), this->in_buf.size(), false);
      this->in_buf.clear();
    }
  else
    { // Just tell the parser to parse the data that was placed into the
      // buffer it provided to us with GetBuffer
      this->parser.parse(size, false);
    }
}

void XmppComponent::on_remote_stream_open(const XmlNode& node)
{
131
  log_debug("XMPP RECEIVING: ", node.to_string());
132 133 134 135 136 137 138 139 140 141
  this->stream_id = node.get_tag("id");
  if (this->stream_id.empty())
    {
      log_error("Error: no attribute 'id' found");
      this->send_stream_error("bad-format", "missing 'id' attribute");
      this->close_document();
      return ;
    }

  // Try to authenticate
142
  auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + get_handshake_digest(this->stream_id, this->secret) + "</handshake>";
143
  log_debug("XMPP SENDING: ", data);
144
  this->send_data(std::move(data));
145 146 147 148
}

void XmppComponent::on_remote_stream_close(const XmlNode& node)
{
149
  log_debug("XMPP RECEIVING: ", node.to_string());
150 151 152 153 154 155 156 157 158 159
  this->doc_open = false;
}

void XmppComponent::reset()
{
  this->parser.reset();
}

void XmppComponent::on_stanza(const Stanza& stanza)
{
160
  log_debug("XMPP RECEIVING: ", stanza.to_string());
161 162 163 164 165 166 167
  std::function<void(const Stanza&)> handler;
  try
    {
      handler = this->stanza_handlers.at(stanza.get_name());
    }
  catch (const std::out_of_range& exception)
    {
168
      log_warning("No handler for stanza of type ", stanza.get_name());
169 170 171 172 173 174 175
      return;
    }
  handler(stanza);
}

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
176 177 178 179 180 181 182
  Stanza node("stream:error");
  {
    XmlSubNode error(node, name);
    error["xmlns"] = STREAM_NS;
    if (!explanation.empty())
      error.set_inner(explanation);
  }
183 184 185 186 187 188 189 190 191
  this->send_stanza(node);
}

void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
                                      const std::string& id, const std::string& error_type,
                                      const std::string& defined_condition, const std::string& text,
                                      const bool fulljid)
{
  Stanza node(kind);
192 193 194 195 196 197 198 199 200 201 202 203 204
  {
    if (!to.empty())
      node["to"] = to;
    if (!from.empty())
      {
        if (fulljid)
          node["from"] = from;
        else
          node["from"] = from + "@" + this->served_hostname;
      }
    if (!id.empty())
      node["id"] = id;
    node["type"] = "error";
205
    {
206 207 208 209 210 211 212 213 214 215 216 217
      XmlSubNode error(node, "error");
      error["type"] = error_type;
      {
        XmlSubNode inner_error(error, defined_condition);
        inner_error["xmlns"] = STANZA_NS;
      }
      if (!text.empty())
        {
          XmlSubNode text_node(error, "text");
          text_node["xmlns"] = STANZA_NS;
          text_node.set_inner(text);
        }
218
    }
219
  }
220 221 222 223 224 225 226 227 228 229
  this->send_stanza(node);
}

void XmppComponent::close_document()
{
  log_debug("XMPP SENDING: </stream:stream>");
  this->send_data("</stream:stream>");
  this->doc_open = false;
}

louiz’'s avatar
louiz’ committed
230
void XmppComponent::handle_handshake(const Stanza&)
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
{
  this->authenticated = true;
  this->ever_auth = true;
  log_info("Authenticated with the XMPP server");
#ifdef SYSTEMD_FOUND
  sd_notify(0, "READY=1");
  // Install an event that sends a keepalive to systemd.  If biboumi crashes
  // or hangs for too long, systemd will restart it.
  uint64_t usec;
  if (sd_watchdog_enabled(0, &usec) > 0)
    {
      TimedEventsManager::instance().add_event(TimedEvent(
             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)),
             []() { sd_notify(0, "WATCHDOG=1"); }));
    }
#endif
  this->after_handshake();
}

void XmppComponent::handle_error(const Stanza& stanza)
{
252
  const XmlNode* text = stanza.get_child("text", STREAMS_NS);
253 254 255
  std::string error_message("Unspecified error");
  if (text)
    error_message = text->get_inner();
256
  log_error("Stream error received from the XMPP server: ", error_message);
257 258 259 260 261 262 263 264 265 266 267 268
#ifdef SYSTEMD_FOUND
  if (!this->ever_auth)
    sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data());
#endif

}

void* XmppComponent::get_receive_buffer(const size_t size) const
{
  return this->parser.get_buffer(size);
}

269 270
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to,
                                 const std::string& type, const bool fulljid, const bool nocopy)
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
  Stanza message("message");
  {
    message["to"] = to;
    if (fulljid)
      message["from"] = from;
    else
      message["from"] = from + "@" + this->served_hostname;
    if (!type.empty())
      message["type"] = type;
    XmlSubNode body_node(message, "body");
    body_node.set_inner(std::get<0>(body));
    if (std::get<1>(body))
      {
        XmlSubNode html(message, "html");
        html["xmlns"] = XHTMLIM_NS;
        // Pass the ownership of the pointer to this xmlnode
        html.add_child(std::move(std::get<1>(body)));
      }
    if (nocopy)
      {
        XmlSubNode private_node(message, "private");
        private_node["xmlns"] = "urn:xmpp:carbons:2";
        XmlSubNode nocopy(message, "no-copy");
        nocopy["xmlns"] = "urn:xmpp:hints";
      }
  }
  this->send_stanza(message);
299 300 301 302 303 304 305 306 307 308
}

void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& nick,
                                   const std::string& realjid,
                                   const std::string& affiliation,
                                   const std::string& role,
                                   const std::string& to,
                                   const bool self)
{
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
  Stanza presence("presence");
  {
    presence["to"] = to;
    presence["from"] = from + "@" + this->served_hostname + "/" + nick;

    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;

    XmlSubNode item(x, "item");
    if (!affiliation.empty())
      item["affiliation"] = affiliation;
    if (!role.empty())
      item["role"] = role;
    if (!realjid.empty())
      {
        const std::string preped_jid = jidprep(realjid);
        if (!preped_jid.empty())
          item["jid"] = preped_jid;
      }

    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
  this->send_stanza(presence);
336 337 338 339 340 341 342
}

void XmppComponent::send_invalid_room_error(const std::string& muc_name,
                                            const std::string& nick,
                                            const std::string& to)
{
  Stanza presence("presence");
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
  {
    if (!muc_name.empty ())
      presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
    else
      presence["from"] = this->served_hostname;
    presence["to"] = to;
    presence["type"] = "error";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_NS;
    XmlSubNode error(presence, "error");
    error["by"] = muc_name + "@" + this->served_hostname;
    error["type"] = "cancel";
    XmlSubNode item_not_found(error, "item-not-found");
    item_not_found["xmlns"] = STANZA_NS;
    XmlSubNode text(error, "text");
    text["xmlns"] = STANZA_NS;
    text["xml:lang"] = "en";
    text.set_inner(muc_name +
                   " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" +
                   this->served_hostname);
  }
364 365 366
  this->send_stanza(presence);
}

367
void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who)
368
{
369 370 371 372 373 374 375 376 377 378 379
  Stanza message("message");
  {
    message["to"] = to;
    if (who.empty())
      message["from"] = from + "@" + this->served_hostname;
    else
      message["from"] = from + "@" + this->served_hostname + "/" + who;
    message["type"] = "groupchat";
    XmlSubNode subject(message, "subject");
    subject.set_inner(std::get<0>(topic));
  }
380 381 382
  this->send_stanza(message);
}

383
void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to, std::string uuid)
384 385 386 387 388 389 390 391
{
  Stanza message("message");
  message["to"] = jid_to;
  if (!nick.empty())
    message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  else // Message from the room itself
    message["from"] = muc_name + "@" + this->served_hostname;
  message["type"] = "groupchat";
392 393 394 395 396 397

  {
    XmlSubNode body(message, "body");
    body.set_inner(std::get<0>(xmpp_body));
  }

398 399
  if (std::get<1>(xmpp_body))
    {
400
      XmlSubNode html(message, "html");
401 402
      html["xmlns"] = XHTMLIM_NS;
      // Pass the ownership of the pointer to this xmlnode
403
      html.add_child(std::move(std::get<1>(xmpp_body)));
404
    }
405 406 407 408 409 410 411 412 413

  if (!uuid.empty())
    {
      XmlSubNode stanza_id(message, "stanza-id");
      stanza_id["xmlns"] = STABLE_ID_NS;
      stanza_id["by"] = muc_name + "@" + this->served_hostname;
      stanza_id["id"] = std::move(uuid);
    }

414 415 416
  this->send_stanza(message);
}

417 418 419 420 421 422 423 424 425 426
void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, std::time_t timestamp)
{
  Stanza message("message");
  message["to"] = jid_to;
  if (!nick.empty())
    message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  else
    message["from"] = muc_name + "@" + this->served_hostname;
  message["type"] = "groupchat";

427 428 429 430 431 432 433 434 435 436
  {
    XmlSubNode body(message, "body");
    body.set_inner(body_txt);
  }
  {
    XmlSubNode delay(message, "delay");
    delay["xmlns"] = DELAY_NS;
    delay["from"] = muc_name + "@" + this->served_hostname;
    delay["stamp"] = utils::to_string(timestamp);
  }
437 438 439 440

  this->send_stanza(message);
}

louiz’'s avatar
louiz’ committed
441
void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
442 443
{
  Stanza presence("presence");
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
  {
    presence["to"] = jid_to;
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
    presence["type"] = "unavailable";
    const std::string message_str = std::get<0>(message);
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
    if (!message_str.empty())
      {
        XmlSubNode status(presence, "status");
        status.set_inner(message_str);
      }
  }
462 463 464 465 466 467 468 469 470 471 472 473
  this->send_stanza(presence);
}

void XmppComponent::send_nick_change(const std::string& muc_name,
                                     const std::string& old_nick,
                                     const std::string& new_nick,
                                     const std::string& affiliation,
                                     const std::string& role,
                                     const std::string& jid_to,
                                     const bool self)
{
  Stanza presence("presence");
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
  {
    presence["to"] = jid_to;
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
    presence["type"] = "unavailable";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["nick"] = new_nick;
    XmlSubNode status(x, "status");
    status["code"] = "303";
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
490 491 492 493 494
  this->send_stanza(presence);

  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
}

495 496
void XmppComponent::kick_user(const std::string& muc_name, const std::string& target, const std::string& txt,
                              const std::string& author, const std::string& jid_to, const bool self)
497 498
{
  Stanza presence("presence");
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
    presence["to"] = jid_to;
    presence["type"] = "unavailable";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["affiliation"] = "none";
    item["role"] = "none";
    XmlSubNode actor(item, "actor");
    actor["nick"] = author;
    actor["jid"] = author; // backward compatibility with old clients
    XmlSubNode reason(item, "reason");
    reason.set_inner(txt);
    XmlSubNode status(x, "status");
    status["code"] = "307";
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
521 522 523 524 525 526 527 528 529
  this->send_stanza(presence);
}

void XmppComponent::send_presence_error(const std::string& muc_name,
                                        const std::string& nickname,
                                        const std::string& jid_to,
                                        const std::string& type,
                                        const std::string& condition,
                                        const std::string& error_code,
530
                                        const std::string& text)
531 532
{
  Stanza presence("presence");
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
    presence["to"] = jid_to;
    presence["type"] = "error";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_NS;
    XmlSubNode error(presence, "error");
    error["by"] = muc_name + "@" + this->served_hostname;
    error["type"] = type;
    if (!text.empty())
      {
        XmlSubNode text_node(error, "text");
        text_node["xmlns"] = STANZA_NS;
        text_node.set_inner(text);
      }
    if (!error_code.empty())
      error["code"] = error_code;
    XmlSubNode subnode(error, condition);
    subnode["xmlns"] = STANZA_NS;
  }
553 554 555 556 557 558 559 560 561 562
  this->send_stanza(presence);
}

void XmppComponent::send_affiliation_role_change(const std::string& muc_name,
                                                 const std::string& target,
                                                 const std::string& affiliation,
                                                 const std::string& role,
                                                 const std::string& jid_to)
{
  Stanza presence("presence");
563 564 565 566 567 568 569 570 571
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
    presence["to"] = jid_to;
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["affiliation"] = affiliation;
    item["role"] = role;
  }
572 573 574 575 576 577 578 579 580 581 582
  this->send_stanza(presence);
}

void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
                                 const std::string& version)
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = jid_from;
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
  {
    XmlSubNode query(iq, "query");
    query["xmlns"] = VERSION_NS;
    if (version.empty())
      {
        {
          XmlSubNode name(query, "name");
          name.set_inner("biboumi");
        }
        {
          XmlSubNode version(query, "version");
          version.set_inner(SOFTWARE_VERSION);
        }
        {
          XmlSubNode os(query, "os");
598
          os.set_inner(utils::get_system_name());
599
        }
600
    }
601
    else
602
    {
603
      XmlSubNode name(query, "name");
604 605
      name.set_inner(version);
    }
606
  }
607 608 609
  this->send_stanza(iq);
}

610 611 612
void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid,
                                             const std::string& from_jid,
                                             const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler)
613 614
{
  Stanza iq("iq");
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
  {
    iq["type"] = "result";
    iq["id"] = id;
    iq["to"] = requester_jid;
    iq["from"] = from_jid;
    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_ITEMS_NS;
    query["node"] = ADHOC_NS;
    for (const auto &kv: adhoc_handler.get_commands())
      {
        if (kv.second.is_admin_only() && !with_admin_only)
          continue;
        XmlSubNode item(query, "item");
        item["jid"] = from_jid;
        item["node"] = kv.first;
        item["name"] = kv.second.name;
      }
  }
633 634 635 636 637 638 639
  this->send_stanza(iq);
}

void XmppComponent::send_iq_version_request(const std::string& from,
                                            const std::string& jid_to)
{
  Stanza iq("iq");
640 641 642 643 644 645 646 647
  {
    iq["type"] = "get";
    iq["id"] = "version_"s + XmppComponent::next_id();
    iq["from"] = from + "@" + this->served_hostname;
    iq["to"] = jid_to;
    XmlSubNode query(iq, "query");
    query["xmlns"] = VERSION_NS;
  }
648 649 650
  this->send_stanza(iq);
}

651 652 653 654 655 656 657 658 659 660
void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid)
{
  Stanza iq("iq");
  iq["from"] = from_full_jid;
  iq["to"] = to_jid;
  iq["id"] = id;
  iq["type"] = "result";
  this->send_stanza(iq);
}

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part)
{
  Stanza iq("iq");
  if (!from_local_part.empty())
    iq["from"] = from_local_part + "@" + this->served_hostname;
  else
    iq["from"] = this->served_hostname;
  iq["to"] = to_jid;
  iq["id"] = id;
  iq["type"] = "result";
  this->send_stanza(iq);
}

std::string XmppComponent::next_id()
{
  char uuid_str[37];
  uuid_t uuid;
  uuid_generate(uuid);
  uuid_unparse(uuid, uuid_str);
  return uuid_str;
}