xmpp_component.cpp 23.7 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

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

XmppComponent::~XmppComponent()
{
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

louiz’'s avatar
louiz’ committed
326 327
void XmppComponent::handle_message(const Stanza& stanza)
{
328 329 330 331 332
  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");

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

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

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

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

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

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

louiz’'s avatar
louiz’ committed
438 439 440 441 442 443 444
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
445 446 447 448 449
#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
450 451
}

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

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

470
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type)
471 472 473 474
{
  XmlNode node("message");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname;
475 476
  if (!type.empty())
    node["type"] = type;
477
  XmlNode body_node("body");
478
  body_node.set_inner(std::get<0>(body));
479 480
  body_node.close();
  node.add_child(std::move(body_node));
481 482 483 484 485 486 487 488 489
  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));
    }
490 491 492 493
  node.close();
  this->send_stanza(node);
}

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

  XmlNode x("x");
507
  x["xmlns"] = MUC_USER_NS;
508 509

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

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

536 537 538 539 540 541 542 543 544 545 546 547 548 549
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
550
  error["type"] = "cancel";
louiz’'s avatar
louiz’ committed
551 552 553 554
  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));
555 556 557 558 559 560 561 562 563 564 565 566 567 568
  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);
}

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

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

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

638 639 640 641 642 643 644
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
645 646 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
{
  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);

672
  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
louiz’'s avatar
louiz’ committed
673
}
674 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

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

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

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

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