xmpp_component.cpp 23.9 KB
Newer Older
1
#include <utils/scopeguard.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

louiz’'s avatar
louiz’ committed
14 15 16 17 18 19
#include <config.h>

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

20 21 22 23
#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"
24
#define MUC_ADMIN_NS     MUC_NS"#admin"
25 26 27
#define DISCO_NS         "http://jabber.org/protocol/disco"
#define DISCO_ITEMS_NS   DISCO_NS"#items"
#define DISCO_INFO_NS    DISCO_NS"#info"
28
#define XHTMLIM_NS       "http://jabber.org/protocol/xhtml-im"
29
#define STANZA_NS        "urn:ietf:params:xml:ns:xmpp-stanzas"
louiz’'s avatar
louiz’ committed
30
#define STREAMS_NS       "urn:ietf:params:xml:ns:xmpp-streams"
31

louiz’'s avatar
louiz’ committed
32 33
unsigned long XmppComponent::current_id = 0;

34
XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret):
35 36
  ever_auth(false),
  last_auth(false),
37 38
  served_hostname(hostname),
  secret(secret),
louiz’'s avatar
louiz’ committed
39 40
  authenticated(false),
  doc_open(false)
41 42 43 44 45 46 47
{
  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));
48
  this->stanza_handlers.emplace(COMPONENT_NS":handshake",
49
                                std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1));
50
  this->stanza_handlers.emplace(COMPONENT_NS":presence",
51
                                std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1));
52
  this->stanza_handlers.emplace(COMPONENT_NS":message",
louiz’'s avatar
louiz’ committed
53
                                std::bind(&XmppComponent::handle_message, this,std::placeholders::_1));
54 55
  this->stanza_handlers.emplace(COMPONENT_NS":iq",
                                std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1));
louiz’'s avatar
louiz’ committed
56 57
  this->stanza_handlers.emplace(STREAM_NS":error",
                                std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
58 59 60 61 62 63
}

XmppComponent::~XmppComponent()
{
}

64
void XmppComponent::start()
65
{
66
  this->connect("127.0.0.1", "5347");
67 68
}

louiz’'s avatar
louiz’ committed
69 70 71 72 73
bool XmppComponent::is_document_open() const
{
  return this->doc_open;
}

74 75
void XmppComponent::send_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
76 77 78
  std::string str = stanza.to_string();
  log_debug("XMPP SENDING: " << str);
  this->send_data(std::move(str));
79 80
}

81 82 83
void XmppComponent::on_connection_failed(const std::string& reason)
{
  log_error("Failed to connect to the XMPP server: " << reason);
louiz’'s avatar
louiz’ committed
84 85 86
#ifdef SYSTEMDDAEMON_FOUND
  sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data());
#endif
87 88
}

89 90
void XmppComponent::on_connected()
{
louiz’'s avatar
louiz’ committed
91
  log_info("connected to XMPP server");
92
  XmlNode node("stream:stream", nullptr);
93 94
  node["xmlns"] = COMPONENT_NS;
  node["xmlns:stream"] = STREAM_NS;
95
  node["to"] = this->served_hostname;
96
  this->send_stanza(node);
louiz’'s avatar
louiz’ committed
97
  this->doc_open = true;
98 99 100
  // 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();
101 102 103 104
}

void XmppComponent::on_connection_close()
{
louiz’'s avatar
louiz’ committed
105
  log_info("XMPP server closed connection");
106 107
}

108
void XmppComponent::parse_in_buffer(const size_t size)
109
{
110 111 112 113 114 115 116 117 118 119 120 121
  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);
    }
122 123
}

louiz’'s avatar
louiz’ committed
124 125 126 127 128 129 130 131
void XmppComponent::shutdown()
{
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
  {
    it->second->shutdown();
  }
}

louiz’'s avatar
louiz’ committed
132 133
void XmppComponent::clean()
{
louiz’'s avatar
louiz’ committed
134 135
  auto it = this->bridges.begin();
  while (it != this->bridges.end())
louiz’'s avatar
louiz’ committed
136 137
  {
    it->second->clean();
138
    if (it->second->active_clients() == 0)
louiz’'s avatar
louiz’ committed
139 140 141
      it = this->bridges.erase(it);
    else
      ++it;
louiz’'s avatar
louiz’ committed
142 143 144
  }
}

145 146
void XmppComponent::on_remote_stream_open(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
147
  log_debug("XMPP DOCUMENT OPEN: " << node.to_string());
148 149
  this->stream_id = node.get_tag("id");
  if (this->stream_id.empty())
150
    {
louiz’'s avatar
louiz’ committed
151
      log_error("Error: no attribute 'id' found");
152 153 154 155 156
      this->send_stream_error("bad-format", "missing 'id' attribute");
      this->close_document();
      return ;
    }

157
  this->last_auth = false;
158
  // Try to authenticate
159 160 161 162 163 164 165 166 167 168 169
  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");
170 171 172 173 174 175 176
  handshake.set_inner(digest);
  handshake.close();
  this->send_stanza(handshake);
}

void XmppComponent::on_remote_stream_close(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
177
  log_debug("XMPP DOCUMENT CLOSE " << node.to_string());
178
  this->doc_open = false;
179 180
}

181 182 183 184 185
void XmppComponent::reset()
{
  this->parser.reset();
}

186 187
void XmppComponent::on_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
188
  log_debug("XMPP RECEIVING: " << stanza.to_string());
189
  std::function<void(const Stanza&)> handler;
190 191
  try
    {
192
      handler = this->stanza_handlers.at(stanza.get_name());
193 194 195
    }
  catch (const std::out_of_range& exception)
    {
louiz’'s avatar
louiz’ committed
196
      log_warning("No handler for stanza of type " << stanza.get_name());
197 198
      return;
    }
199
  handler(stanza);
200 201 202 203 204 205
}

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
  XmlNode node("stream:error", nullptr);
  XmlNode error(name, nullptr);
206
  error["xmlns"] = STREAM_NS;
207 208 209 210 211 212 213 214
  if (!explanation.empty())
    error.set_inner(explanation);
  error.close();
  node.add_child(std::move(error));
  node.close();
  this->send_stanza(node);
}

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
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)
{
  Stanza node(kind);
  if (!to.empty())
    node["to"] = to;
  if (!from.empty())
    node["from"] = from;
  if (!id.empty())
    node["id"] = id;
  node["type"] = "error";
  XmlNode error("error");
  error["type"] = error_type;
  XmlNode inner_error(defined_condition);
  inner_error["xmlns"] = STANZA_NS;
  inner_error.close();
  error.add_child(std::move(inner_error));
  if (!text.empty())
    {
      XmlNode text_node("text");
      text_node["xmlns"] = STANZA_NS;
      text_node.set_inner(text);
      text_node.close();
      error.add_child(std::move(text_node));
    }
  error.close();
  node.add_child(std::move(error));
  node.close();
  this->send_stanza(node);
}

247 248
void XmppComponent::close_document()
{
louiz’'s avatar
louiz’ committed
249
  log_debug("XMPP SENDING: </stream:stream>");
250
  this->send_data("</stream:stream>");
louiz’'s avatar
louiz’ committed
251
  this->doc_open = false;
252 253 254 255
}

void XmppComponent::handle_handshake(const Stanza& stanza)
{
256
  (void)stanza;
257
  this->authenticated = true;
258 259
  this->ever_auth = true;
  this->last_auth = true;
louiz’'s avatar
louiz’ committed
260
  log_info("Authenticated with the XMPP server");
louiz’'s avatar
louiz’ committed
261 262 263
#ifdef SYSTEMDDAEMON_FOUND
  sd_notify(0, "READY=1");
#endif
264
}
265 266 267

void XmppComponent::handle_presence(const Stanza& stanza)
{
268 269 270 271 272 273
  std::string from = stanza.get_tag("from");
  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
274
  if (from.empty())
275 276 277 278 279 280
    {
      log_warning("Received an invalid presence stanza: tag 'from' is missing.");
      return;
    }
  if (to_str.empty())
    {
281
      this->send_stanza_error("presence", from, this->served_hostname, id,
282 283 284 285 286 287
                              "modify", "bad-request", "Missing 'to' tag");
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);
288
  Iid iid(to.local);
289 290 291 292 293 294 295 296 297

  // 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");
298
  utils::ScopeGuard stanza_error([&](){
299
      this->send_stanza_error("presence", from, to_str, id,
300 301
                              error_type, error_name, "");
        });
louiz’'s avatar
louiz’ committed
302

louiz’'s avatar
louiz’ committed
303 304 305
  if (!iid.server.empty())
    { // presence toward a MUC that corresponds to an irc channel, or a
      // dummy channel if iid.chan is empty
louiz’'s avatar
louiz’ committed
306
      if (type.empty())
louiz’'s avatar
louiz’ committed
307 308 309 310
        {
          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);
311
          bridge->join_irc_channel(iid, to.resource);
louiz’'s avatar
louiz’ committed
312
        }
louiz’'s avatar
louiz’ committed
313 314
      else if (type == "unavailable")
        {
315
          XmlNode* status = stanza.get_child(COMPONENT_NS":status");
louiz’'s avatar
louiz’ committed
316 317 318
          bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
        }
    }
319 320 321 322
  else
    {
      // An user wants to join an invalid IRC channel, return a presence error to him
      if (type.empty())
323
        this->send_invalid_room_error(to.local, to.resource, from);
324
    }
325
  stanza_error.disable();
326 327
}

louiz’'s avatar
louiz’ committed
328 329
void XmppComponent::handle_message(const Stanza& stanza)
{
330 331 332 333 334
  std::string from = stanza.get_tag("from");
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

335 336
  if (from.empty())
    return;
337
  if (type.empty())
338
    type = "normal";
339 340 341
  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);
  Iid iid(to.local);
342 343 344

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
345
  utils::ScopeGuard stanza_error([&](){
346
      this->send_stanza_error("message", from, to_str, id,
347 348
                              error_type, error_name, "");
        });
349
  XmlNode* body = stanza.get_child(COMPONENT_NS":body");
350
  if (type == "groupchat")
louiz’'s avatar
louiz’ committed
351 352 353 354
    {
      if (to.resource.empty())
        if (body && !body->get_inner().empty())
          bridge->send_channel_message(iid, body->get_inner());
355 356 357
      XmlNode* subject = stanza.get_child(COMPONENT_NS":subject");
      if (subject)
        bridge->set_channel_topic(iid, subject->get_inner());
louiz’'s avatar
louiz’ committed
358
    }
louiz’'s avatar
louiz’ committed
359 360 361 362 363
  else
    {
      if (body && !body->get_inner().empty())
        bridge->send_private_message(iid, body->get_inner());
    }
364
  stanza_error.disable();
louiz’'s avatar
louiz’ committed
365 366
}

367 368
void XmppComponent::handle_iq(const Stanza& stanza)
{
369 370 371 372 373
  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");

374 375
  if (from.empty())
    return;
376 377
  if (id.empty() || to_str.empty() || type.empty())
    {
378
      this->send_stanza_error("iq", from, this->served_hostname, id,
379
                              "modify", "bad-request", "");
380 381 382 383
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
384
  Jid to(to_str);
385 386 387

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
388
  utils::ScopeGuard stanza_error([&](){
389
      this->send_stanza_error("iq", from, to_str, id,
390 391
                              error_type, error_name, "");
        });
392 393 394 395 396
  if (type == "set")
    {
      XmlNode* query;
      if ((query = stanza.get_child(MUC_ADMIN_NS":query")))
        {
397 398
          const XmlNode* child = query->get_child(MUC_ADMIN_NS":item");
          if (child)
399
            {
400 401
              std::string nick = child->get_tag("nick");
              std::string role = child->get_tag("role");
402 403 404 405 406 407 408 409 410
              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);
                }
411 412 413 414 415 416
              else
                {
                  error_type = "cancel";
                  error_name = "feature-not-implemented";
                  return;
                }
417 418 419
            }
        }
    }
420 421 422 423 424 425 426 427 428 429 430
  else if (type == "get")
    {
      XmlNode* query;
      if ((query = stanza.get_child(DISCO_INFO_NS":query")))
        { // Disco info
          if (to_str == this->served_hostname)
            { // On the gateway itself
              this->send_self_disco_info(id, from);
            }
        }
    }
431 432 433 434 435 436 437
  else
    {
      error_type = "cancel";
      error_name = "feature-not-implemented";
      return;
    }
  stanza_error.disable();
438 439
}

louiz’'s avatar
louiz’ committed
440 441 442 443 444 445 446
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);
louiz’'s avatar
louiz’ committed
447 448 449 450 451
#ifdef SYSTEMDDAEMON_FOUND
  if (!this->ever_auth)
    sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data());
#endif

louiz’'s avatar
louiz’ committed
452 453
}

454 455 456 457 458 459 460 461 462 463 464 465 466
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();
    }
}

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

472
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type)
473 474 475 476
{
  XmlNode node("message");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname;
477 478
  if (!type.empty())
    node["type"] = type;
479
  XmlNode body_node("body");
480
  body_node.set_inner(std::get<0>(body));
481 482
  body_node.close();
  node.add_child(std::move(body_node));
483 484 485 486 487 488 489 490 491
  if (std::get<1>(body))
    {
      XmlNode html("html");
      html["xmlns"] = XHTMLIM_NS;
      // Pass the ownership of the pointer to this xmlnode
      html.add_child(std::get<1>(body).release());
      html.close();
      node.add_child(std::move(html));
    }
492 493 494 495
  node.close();
  this->send_stanza(node);
}

496 497 498
void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& nick,
                                   const std::string& realjid,
499 500 501 502
                                   const std::string& affiliation,
                                   const std::string& role,
                                   const std::string& to,
                                   const bool self)
503 504 505 506 507 508
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
509
  x["xmlns"] = MUC_USER_NS;
510 511

  XmlNode item("item");
512 513 514 515
  if (!affiliation.empty())
    item["affiliation"] = affiliation;
  if (!role.empty())
    item["role"] = role;
516 517 518 519 520 521
  if (!realjid.empty())
    {
      const std::string preped_jid = jidprep(realjid);
      if (!preped_jid.empty())
        item["jid"] = preped_jid;
    }
522
  item.close();
523
  x.add_child(std::move(item));
524

525 526 527 528 529 530 531
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      status.close();
      x.add_child(std::move(status));
    }
532 533 534 535 536 537
  x.close();
  node.add_child(std::move(x));
  node.close();
  this->send_stanza(node);
}

538 539 540 541 542 543 544 545 546 547 548 549 550 551
void XmppComponent::send_invalid_room_error(const std::string& muc_name,
                                            const std::string& nick,
                                            const std::string& to)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["to"] = to;
  presence["type"] = "error";
  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;
louiz’'s avatar
Idem  
louiz’ committed
552
  error["type"] = "cancel";
louiz’'s avatar
louiz’ committed
553 554 555 556
  XmlNode item_not_found("item-not-found");
  item_not_found["xmlns"] = STANZA_NS;
  item_not_found.close();
  error.add_child(std::move(item_not_found));
557 558 559 560 561 562 563 564 565 566 567 568 569 570
  XmlNode text("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);
  text.close();
  error.add_child(std::move(text));
  error.close();
  presence.add_child(std::move(error));
  presence.close();
  this->send_stanza(presence);
}

571
void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to)
572 573 574 575 576 577
{
  XmlNode message("message");
  message["to"] = to;
  message["from"] = from + "@" + this->served_hostname;
  message["type"] = "groupchat";
  XmlNode subject("subject");
578
  subject.set_inner(std::get<0>(topic));
579 580 581 582 583
  subject.close();
  message.add_child(std::move(subject));
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
584

585
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
586 587 588
{
  Stanza message("message");
  message["to"] = jid_to;
louiz’'s avatar
louiz’ committed
589 590 591 592
  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
593 594
  message["type"] = "groupchat";
  XmlNode body("body");
595
  body.set_inner(std::get<0>(xmpp_body));
louiz’'s avatar
louiz’ committed
596 597
  body.close();
  message.add_child(std::move(body));
598 599 600 601 602 603 604 605 606
  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
607 608 609
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
610

611
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
612 613 614 615 616
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["type"] = "unavailable";
617
  const std::string message_str = std::get<0>(message);
louiz’'s avatar
louiz’ committed
618 619 620 621 622 623 624 625 626 627 628 629
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      status.close();
      x.add_child(std::move(status));
    }
  x.close();
  presence.add_child(std::move(x));
  if (!message_str.empty())
louiz’'s avatar
louiz’ committed
630 631
    {
      XmlNode status("status");
louiz’'s avatar
louiz’ committed
632
      status.set_inner(message_str);
louiz’'s avatar
louiz’ committed
633 634 635 636 637 638
      status.close();
      presence.add_child(std::move(status));
    }
  presence.close();
  this->send_stanza(presence);
}
louiz’'s avatar
louiz’ committed
639

640 641 642 643 644 645 646
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
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
{
  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);

674
  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
louiz’'s avatar
louiz’ committed
675
}
676 677 678 679 680 681 682 683 684 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 710 711

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);
}
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

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);
}
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758

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);
}
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787

void XmppComponent::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";
  identity.close();
  query.add_child(std::move(identity));
  for (const std::string& ns: {"http://jabber.org/protocol/disco#info",
                                    "http://jabber.org/protocol/muc"})
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      feature.close();
      query.add_child(std::move(feature));
    }
  query.close();
  iq.add_child(std::move(query));
  iq.close();
  this->send_stanza(iq);
}
louiz’'s avatar
louiz’ committed
788 789 790 791 792

std::string XmppComponent::next_id()
{
  return std::to_string(XmppComponent::current_id++);
}