xmpp_component.cpp 16.8 KB
Newer Older
1
#include <utils/make_unique.hpp>
louiz’'s avatar
louiz’ committed
2
#include <logger/logger.hpp>
3

4
#include <xmpp/xmpp_component.hpp>
5
#include <xmpp/jid.hpp>
6

7 8
#include <utils/sha1.hpp>

louiz’'s avatar
louiz’ committed
9
#include <stdexcept>
10 11
#include <iostream>

12
#include <stdio.h>
13

14 15 16 17
#define STREAM_NS        "http://etherx.jabber.org/streams"
#define COMPONENT_NS     "jabber:component:accept"
#define MUC_NS           "http://jabber.org/protocol/muc"
#define MUC_USER_NS      MUC_NS"#user"
18
#define MUC_ADMIN_NS     MUC_NS"#admin"
19 20 21
#define DISCO_NS         "http://jabber.org/protocol/disco"
#define DISCO_ITEMS_NS   DISCO_NS"#items"
#define DISCO_INFO_NS    DISCO_NS"#info"
22
#define XHTMLIM_NS       "http://jabber.org/protocol/xhtml-im"
23
#define STANZA_NS        "urn:ietf:params:xml:ns:xmpp-stanzas"
louiz’'s avatar
louiz’ committed
24
#define STREAMS_NS       "urn:ietf:params:xml:ns:xmpp-streams"
25

26 27 28
XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret):
  served_hostname(hostname),
  secret(secret),
louiz’'s avatar
louiz’ committed
29 30
  authenticated(false),
  doc_open(false)
31 32 33 34 35 36 37
{
  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));
38
  this->stanza_handlers.emplace(COMPONENT_NS":handshake",
39
                                std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1));
40
  this->stanza_handlers.emplace(COMPONENT_NS":presence",
41
                                std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1));
42
  this->stanza_handlers.emplace(COMPONENT_NS":message",
louiz’'s avatar
louiz’ committed
43
                                std::bind(&XmppComponent::handle_message, this,std::placeholders::_1));
44 45
  this->stanza_handlers.emplace(COMPONENT_NS":iq",
                                std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1));
louiz’'s avatar
louiz’ committed
46 47
  this->stanza_handlers.emplace(STREAM_NS":error",
                                std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
48 49 50 51 52 53
}

XmppComponent::~XmppComponent()
{
}

54
void XmppComponent::start()
55
{
56
  this->connect("127.0.0.1", "5347");
57 58
}

louiz’'s avatar
louiz’ committed
59 60 61 62 63
bool XmppComponent::is_document_open() const
{
  return this->doc_open;
}

64 65
void XmppComponent::send_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
66 67 68
  std::string str = stanza.to_string();
  log_debug("XMPP SENDING: " << str);
  this->send_data(std::move(str));
69 70
}

71 72 73 74 75
void XmppComponent::on_connection_failed(const std::string& reason)
{
  log_error("Failed to connect to the XMPP server: " << reason);
}

76 77
void XmppComponent::on_connected()
{
louiz’'s avatar
louiz’ committed
78
  log_info("connected to XMPP server");
79
  XmlNode node("stream:stream", nullptr);
80 81
  node["xmlns"] = COMPONENT_NS;
  node["xmlns:stream"] = STREAM_NS;
82
  node["to"] = this->served_hostname;
83
  this->send_stanza(node);
louiz’'s avatar
louiz’ committed
84
  this->doc_open = true;
85 86 87 88
}

void XmppComponent::on_connection_close()
{
louiz’'s avatar
louiz’ committed
89
  log_info("XMPP server closed connection");
90 91
}

92
void XmppComponent::parse_in_buffer(const size_t size)
93
{
94 95 96 97 98 99 100 101 102 103 104 105
  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);
    }
106 107
}

louiz’'s avatar
louiz’ committed
108 109 110 111 112 113 114 115
void XmppComponent::shutdown()
{
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
  {
    it->second->shutdown();
  }
}

louiz’'s avatar
louiz’ committed
116 117
void XmppComponent::clean()
{
louiz’'s avatar
louiz’ committed
118 119
  auto it = this->bridges.begin();
  while (it != this->bridges.end())
louiz’'s avatar
louiz’ committed
120 121
  {
    it->second->clean();
122
    if (it->second->active_clients() == 0)
louiz’'s avatar
louiz’ committed
123 124 125
      it = this->bridges.erase(it);
    else
      ++it;
louiz’'s avatar
louiz’ committed
126 127 128
  }
}

129 130
void XmppComponent::on_remote_stream_open(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
131
  log_debug("XMPP DOCUMENT OPEN: " << node.to_string());
132 133 134 135 136 137
  try
    {
      this->stream_id = node["id"];
    }
  catch (const AttributeNotFound& e)
    {
louiz’'s avatar
louiz’ committed
138
      log_error("Error: no attribute 'id' found");
139 140 141 142 143 144
      this->send_stream_error("bad-format", "missing 'id' attribute");
      this->close_document();
      return ;
    }

  // Try to authenticate
145 146 147 148 149 150 151 152 153 154 155
  char digest[HASH_LENGTH * 2 + 1];
  sha1nfo sha1;
  sha1_init(&sha1);
  sha1_write(&sha1, this->stream_id.data(), this->stream_id.size());
  sha1_write(&sha1, this->secret.data(),  this->secret.size());
  const uint8_t* result = sha1_result(&sha1);
  for (int i=0; i < HASH_LENGTH; i++)
    sprintf(digest + (i*2), "%02x", result[i]);
  digest[HASH_LENGTH * 2] = '\0';

  Stanza handshake("handshake");
156 157 158 159 160 161 162
  handshake.set_inner(digest);
  handshake.close();
  this->send_stanza(handshake);
}

void XmppComponent::on_remote_stream_close(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
163
  log_debug("XMPP DOCUMENT CLOSE " << node.to_string());
164 165 166 167
}

void XmppComponent::on_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
168
  log_debug("XMPP RECEIVING: " << stanza.to_string());
169
  std::function<void(const Stanza&)> handler;
170 171
  try
    {
172
      handler = this->stanza_handlers.at(stanza.get_name());
173 174 175
    }
  catch (const std::out_of_range& exception)
    {
louiz’'s avatar
louiz’ committed
176
      log_warning("No handler for stanza of type " << stanza.get_name());
177 178
      return;
    }
179
  handler(stanza);
180 181 182 183 184 185
}

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
  XmlNode node("stream:error", nullptr);
  XmlNode error(name, nullptr);
186
  error["xmlns"] = STREAM_NS;
187 188 189 190 191 192 193 194 195 196
  if (!explanation.empty())
    error.set_inner(explanation);
  error.close();
  node.add_child(std::move(error));
  node.close();
  this->send_stanza(node);
}

void XmppComponent::close_document()
{
louiz’'s avatar
louiz’ committed
197
  log_debug("XMPP SENDING: </stream:stream>");
198
  this->send_data("</stream:stream>");
louiz’'s avatar
louiz’ committed
199
  this->doc_open = false;
200 201 202 203
}

void XmppComponent::handle_handshake(const Stanza& stanza)
{
204
  (void)stanza;
205
  this->authenticated = true;
louiz’'s avatar
louiz’ committed
206
  log_info("Authenticated with the XMPP server");
207
}
208 209 210 211 212 213

void XmppComponent::handle_presence(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  Iid iid(to.local);
louiz’'s avatar
louiz’ committed
214 215 216 217 218 219 220 221 222
  std::string type;
  try {
    type = stanza["type"];
  }
  catch (const AttributeNotFound&) {}

  if (!iid.chan.empty() && !iid.chan.empty())
    { // presence toward a MUC that corresponds to an irc channel
      if (type.empty())
louiz’'s avatar
louiz’ committed
223 224 225 226
        {
          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);
227
          bridge->join_irc_channel(iid, to.resource);
louiz’'s avatar
louiz’ committed
228
        }
louiz’'s avatar
louiz’ committed
229 230
      else if (type == "unavailable")
        {
231
          XmlNode* status = stanza.get_child(COMPONENT_NS":status");
louiz’'s avatar
louiz’ committed
232 233 234
          bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
        }
    }
235 236
}

louiz’'s avatar
louiz’ committed
237 238 239 240 241
void XmppComponent::handle_message(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  Iid iid(to.local);
242
  XmlNode* body = stanza.get_child(COMPONENT_NS":body");
louiz’'s avatar
louiz’ committed
243 244 245 246 247
  if (stanza["type"] == "groupchat")
    {
      if (to.resource.empty())
        if (body && !body->get_inner().empty())
          bridge->send_channel_message(iid, body->get_inner());
248 249 250
      XmlNode* subject = stanza.get_child(COMPONENT_NS":subject");
      if (subject)
        bridge->set_channel_topic(iid, subject->get_inner());
louiz’'s avatar
louiz’ committed
251
    }
louiz’'s avatar
louiz’ committed
252 253 254 255 256
  else
    {
      if (body && !body->get_inner().empty())
        bridge->send_private_message(iid, body->get_inner());
    }
louiz’'s avatar
louiz’ committed
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
void XmppComponent::handle_iq(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  std::string type;
  try {
    type = stanza["type"];
  }
  catch (const AttributeNotFound&)
    { return; }
  if (type == "set")
    {
      XmlNode* query;
      if ((query = stanza.get_child(MUC_ADMIN_NS":query")))
        {
          XmlNode* child;
          if ((child = query->get_child(MUC_ADMIN_NS":item")))
            {
              std::string nick;
              std::string role;
              try {
                nick = (*child)["nick"];
                role = (*child)["role"];
              }
              catch (const AttributeNotFound&)
                { return; }
              if (!nick.empty() && role == "none")
                {
                  std::string reason;
                  XmlNode* reason_el = child->get_child(MUC_ADMIN_NS":reason");
                  if (reason_el)
                    reason = reason_el->get_inner();
                  Iid iid(to.local);
                  bridge->send_irc_kick(iid, nick, reason);
                }
            }
        }
    }
}

louiz’'s avatar
louiz’ committed
299 300 301 302 303 304 305 306 307
void XmppComponent::handle_error(const Stanza& stanza)
{
  XmlNode* text = stanza.get_child(STREAMS_NS":text");
  std::string error_message("Unspecified error");
  if (text)
    error_message = text->get_inner();
  log_error("Stream error received from the XMPP server: " << error_message);
}

308 309 310 311 312 313 314 315 316 317 318 319 320
Bridge* XmppComponent::get_user_bridge(const std::string& user_jid)
{
  try
    {
      return this->bridges.at(user_jid).get();
    }
  catch (const std::out_of_range& exception)
    {
      this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller));
      return this->bridges.at(user_jid).get();
    }
}

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

326
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to)
327 328 329 330 331
{
  XmlNode node("message");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname;
  XmlNode body_node("body");
332
  body_node.set_inner(std::get<0>(body));
333 334 335 336 337 338
  body_node.close();
  node.add_child(std::move(body_node));
  node.close();
  this->send_stanza(node);
}

339 340 341
void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& nick,
                                   const std::string& realjid,
342 343 344 345
                                   const std::string& affiliation,
                                   const std::string& role,
                                   const std::string& to,
                                   const bool self)
346 347 348 349 350 351
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
352
  x["xmlns"] = MUC_USER_NS;
353 354

  XmlNode item("item");
355 356 357 358
  if (!affiliation.empty())
    item["affiliation"] = affiliation;
  if (!role.empty())
    item["role"] = role;
359 360 361 362 363 364
  if (!realjid.empty())
    {
      const std::string preped_jid = jidprep(realjid);
      if (!preped_jid.empty())
        item["jid"] = preped_jid;
    }
365
  item.close();
366
  x.add_child(std::move(item));
367

368 369 370 371 372 373 374
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      status.close();
      x.add_child(std::move(status));
    }
375 376 377 378 379 380
  x.close();
  node.add_child(std::move(x));
  node.close();
  this->send_stanza(node);
}

381
void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to)
382 383 384 385 386 387
{
  XmlNode message("message");
  message["to"] = to;
  message["from"] = from + "@" + this->served_hostname;
  message["type"] = "groupchat";
  XmlNode subject("subject");
388
  subject.set_inner(std::get<0>(topic));
389 390 391 392 393
  subject.close();
  message.add_child(std::move(subject));
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
394

395
void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to)
louiz’'s avatar
louiz’ committed
396 397 398
{
  Stanza message("message");
  message["to"] = jid_to;
louiz’'s avatar
louiz’ committed
399 400 401 402
  if (!nick.empty())
    message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  else // Message from the room itself
    message["from"] = muc_name + "@" + this->served_hostname;
louiz’'s avatar
louiz’ committed
403 404
  message["type"] = "groupchat";
  XmlNode body("body");
405
  body.set_inner(std::get<0>(xmpp_body));
louiz’'s avatar
louiz’ committed
406 407
  body.close();
  message.add_child(std::move(body));
408 409 410 411 412 413 414 415 416
  if (std::get<1>(xmpp_body))
    {
      XmlNode html("html");
      html["xmlns"] = XHTMLIM_NS;
      // Pass the ownership of the pointer to this xmlnode
      html.add_child(std::get<1>(xmpp_body).release());
      html.close();
      message.add_child(std::move(html));
    }
louiz’'s avatar
louiz’ committed
417 418 419
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
420

421
void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
louiz’'s avatar
louiz’ committed
422 423 424 425 426
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["type"] = "unavailable";
427
  const std::string message_str = std::get<0>(message);
428
  if (!message_str.empty() || self)
louiz’'s avatar
louiz’ committed
429 430
    {
      XmlNode status("status");
431 432
      if (!message_str.empty())
        status.set_inner(message_str);
louiz’'s avatar
louiz’ committed
433 434 435 436 437 438 439 440
      if (self)
        status["code"] = "110";
      status.close();
      presence.add_child(std::move(status));
    }
  presence.close();
  this->send_stanza(presence);
}
louiz’'s avatar
louiz’ committed
441

442 443 444 445 446 447 448
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)
louiz’'s avatar
louiz’ committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
  presence["type"] = "unavailable";
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["nick"] = new_nick;
  item.close();
  x.add_child(std::move(item));
  XmlNode status("status");
  status["code"] = "303";
  status.close();
  x.add_child(std::move(status));
  if (self)
    {
      XmlNode status2("status");
      status2["code"] = "110";
      status2.close();
      x.add_child(std::move(status2));
    }
  x.close();
  presence.add_child(std::move(x));
  presence.close();
  this->send_stanza(presence);

476
  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
louiz’'s avatar
louiz’ committed
477
}
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513

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)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
  presence["to"] = jid_to;
  presence["type"] = "unavailable";
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["affiliation"] = "none";
  item["role"] = "none";
  XmlNode actor("actor");
  actor["nick"] = author;
  actor["jid"] = author; // backward compatibility with old clients
  actor.close();
  item.add_child(std::move(actor));
  XmlNode reason("reason");
  reason.set_inner(txt);
  reason.close();
  item.add_child(std::move(reason));
  item.close();
  x.add_child(std::move(item));
  XmlNode status("status");
  status["code"] = "307";
  status.close();
  x.add_child(std::move(status));
  x.close();
  presence.add_child(std::move(x));
  presence.close();
  this->send_stanza(presence);
}
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538

void XmppComponent::send_nickname_conflict_error(const std::string& muc_name,
                                                 const std::string& nickname,
                                                 const std::string& jid_to)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
  presence["to"] = jid_to;
  XmlNode x("x");
  x["xmlns"] = MUC_NS;
  x.close();
  presence.add_child(std::move(x));
  XmlNode error("error");
  error["by"] = muc_name + "@" + this->served_hostname;
  error["type"] = "cancel";
  error["code"] = "409";
  XmlNode conflict("conflict");
  conflict["xmlns"] = STANZA_NS;
  conflict.close();
  error.add_child(std::move(conflict));
  error.close();
  presence.add_child(std::move(error));
  presence.close();
  this->send_stanza(presence);
}
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560

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");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
  presence["to"] = jid_to;
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["affiliation"] = affiliation;
  item["role"] = role;
  item.close();
  x.add_child(std::move(item));
  x.close();
  presence.add_child(std::move(x));
  presence.close();
  this->send_stanza(presence);
}