xmpp_component.cpp 33.6 KB
Newer Older
1
#include <utils/timed_events.hpp>
2
#include <utils/scopeguard.hpp>
3
#include <utils/tolower.hpp>
louiz’'s avatar
louiz’ committed
4
#include <logger/logger.hpp>
5

6
#include <xmpp/xmpp_component.hpp>
7
#include <config/config.hpp>
8
#include <xmpp/jid.hpp>
9
10
#include <utils/sha1.hpp>

louiz’'s avatar
louiz’ committed
11
#include <stdexcept>
12
13
#include <iostream>

14
#include <stdio.h>
15

louiz’'s avatar
louiz’ committed
16
17
#include <config.h>

18
19
#include <uuid.h>

louiz’'s avatar
louiz’ committed
20
21
22
23
#ifdef SYSTEMDDAEMON_FOUND
# include <systemd/sd-daemon.h>
#endif

24
using namespace std::string_literals;
25

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

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

XmppComponent::~XmppComponent()
{
}

71
void XmppComponent::start()
72
{
73
  this->connect("127.0.0.1", Config::get("port", "5347"), false);
74
75
}

louiz’'s avatar
louiz’ committed
76
77
78
79
80
bool XmppComponent::is_document_open() const
{
  return this->doc_open;
}

81
82
void XmppComponent::send_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
83
84
85
  std::string str = stanza.to_string();
  log_debug("XMPP SENDING: " << str);
  this->send_data(std::move(str));
86
87
}

88
89
90
void XmppComponent::on_connection_failed(const std::string& reason)
{
  log_error("Failed to connect to the XMPP server: " << reason);
louiz’'s avatar
louiz’ committed
91
92
93
#ifdef SYSTEMDDAEMON_FOUND
  sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data());
#endif
94
95
}

96
97
void XmppComponent::on_connected()
{
louiz’'s avatar
louiz’ committed
98
  log_info("connected to XMPP server");
99
100
  XmlNode node("", nullptr);
  node.set_name("stream:stream");
101
102
  node["xmlns"] = COMPONENT_NS;
  node["xmlns:stream"] = STREAM_NS;
103
  node["to"] = this->served_hostname;
104
  this->send_stanza(node);
louiz’'s avatar
louiz’ committed
105
  this->doc_open = true;
106
107
108
  // 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();
109
110
}

111
void XmppComponent::on_connection_close(const std::string& error)
112
{
113
114
115
116
117
118
119
120
  if (error.empty())
    {
      log_info("XMPP server closed connection");
    }
  else
    {
      log_info("XMPP server closed connection: " << error);
    }
121
122
}

123
void XmppComponent::parse_in_buffer(const size_t size)
124
{
125
126
127
128
129
130
131
132
133
134
135
136
  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);
    }
137
138
}

louiz’'s avatar
louiz’ committed
139
140
141
142
void XmppComponent::shutdown()
{
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
  {
143
    it->second->shutdown("Gateway shutdown");
louiz’'s avatar
louiz’ committed
144
145
146
  }
}

louiz’'s avatar
louiz’ committed
147
148
void XmppComponent::clean()
{
louiz’'s avatar
louiz’ committed
149
150
  auto it = this->bridges.begin();
  while (it != this->bridges.end())
louiz’'s avatar
louiz’ committed
151
152
  {
    it->second->clean();
153
    if (it->second->active_clients() == 0)
louiz’'s avatar
louiz’ committed
154
155
156
      it = this->bridges.erase(it);
    else
      ++it;
louiz’'s avatar
louiz’ committed
157
158
159
  }
}

160
161
void XmppComponent::on_remote_stream_open(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
162
  log_debug("XMPP DOCUMENT OPEN: " << node.to_string());
163
164
  this->stream_id = node.get_tag("id");
  if (this->stream_id.empty())
165
    {
louiz’'s avatar
louiz’ committed
166
      log_error("Error: no attribute 'id' found");
167
168
169
170
171
      this->send_stream_error("bad-format", "missing 'id' attribute");
      this->close_document();
      return ;
    }

172
  this->last_auth = false;
173
  // Try to authenticate
174
175
176
177
178
179
180
181
182
183
  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';

184
  Stanza handshake(COMPONENT_NS":handshake");
185
186
187
188
189
190
191
  handshake.set_inner(digest);
  handshake.close();
  this->send_stanza(handshake);
}

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

196
197
198
199
200
void XmppComponent::reset()
{
  this->parser.reset();
}

201
202
void XmppComponent::on_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
203
  log_debug("XMPP RECEIVING: " << stanza.to_string());
204
  std::function<void(const Stanza&)> handler;
205
206
  try
    {
207
      handler = this->stanza_handlers.at(stanza.get_name());
208
209
210
    }
  catch (const std::out_of_range& exception)
    {
louiz’'s avatar
louiz’ committed
211
      log_warning("No handler for stanza of type " << stanza.get_name());
212
213
      return;
    }
214
  handler(stanza);
215
216
217
218
219
220
}

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
  XmlNode node("stream:error", nullptr);
  XmlNode error(name, nullptr);
221
  error["xmlns"] = STREAM_NS;
222
223
224
225
226
227
228
229
  if (!explanation.empty())
    error.set_inner(explanation);
  error.close();
  node.add_child(std::move(error));
  node.close();
  this->send_stanza(node);
}

230
void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
231
232
233
                                      const std::string& id, const std::string& error_type,
                                      const std::string& defined_condition, const std::string& text,
                                      const bool fulljid)
234
235
236
237
238
{
  Stanza node(kind);
  if (!to.empty())
    node["to"] = to;
  if (!from.empty())
239
240
241
242
243
244
    {
      if (fulljid)
        node["from"] = from;
      else
        node["from"] = from + "@" + this->served_hostname;
    }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  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);
}

268
269
void XmppComponent::close_document()
{
louiz’'s avatar
louiz’ committed
270
  log_debug("XMPP SENDING: </stream:stream>");
271
  this->send_data("</stream:stream>");
louiz’'s avatar
louiz’ committed
272
  this->doc_open = false;
273
274
275
276
}

void XmppComponent::handle_handshake(const Stanza& stanza)
{
277
  (void)stanza;
278
  this->authenticated = true;
279
280
  this->ever_auth = true;
  this->last_auth = true;
louiz’'s avatar
louiz’ committed
281
  log_info("Authenticated with the XMPP server");
louiz’'s avatar
louiz’ committed
282
283
#ifdef SYSTEMDDAEMON_FOUND
  sd_notify(0, "READY=1");
284
285
286
287
288
289
290
291
292
293
  // 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)
    {
      std::chrono::microseconds delay(usec);
      TimedEventsManager::instance().add_event(TimedEvent(
             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)),
             []() { sd_notify(0, "WATCHDOG=1"); }));
    }
louiz’'s avatar
louiz’ committed
294
#endif
295
}
296
297
298

void XmppComponent::handle_presence(const Stanza& stanza)
{
299
300
301
302
303
304
  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
305
  if (from.empty())
306
307
308
309
310
311
    {
      log_warning("Received an invalid presence stanza: tag 'from' is missing.");
      return;
    }
  if (to_str.empty())
    {
312
      this->send_stanza_error("presence", from, this->served_hostname, id,
313
314
315
316
317
318
                              "modify", "bad-request", "Missing 'to' tag");
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);
319
  Iid iid(to.local);
320
321
322
323
324
325
326
327
328

  // 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");
329
  utils::ScopeGuard stanza_error([&](){
330
      this->send_stanza_error("presence", from, to_str, id,
331
332
                              error_type, error_name, "");
        });
louiz’'s avatar
louiz’ committed
333

louiz’'s avatar
louiz’ committed
334
  if (iid.is_channel && !iid.get_server().empty())
louiz’'s avatar
louiz’ committed
335
336
    { // presence toward a MUC that corresponds to an irc channel, or a
      // dummy channel if iid.chan is empty
louiz’'s avatar
louiz’ committed
337
      if (type.empty())
louiz’'s avatar
louiz’ committed
338
339
340
341
        {
          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);
342
          bridge->join_irc_channel(iid, to.resource);
louiz’'s avatar
louiz’ committed
343
        }
louiz’'s avatar
louiz’ committed
344
345
      else if (type == "unavailable")
        {
346
          XmlNode* status = stanza.get_child("status", COMPONENT_NS);
louiz’'s avatar
louiz’ committed
347
348
349
          bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
        }
    }
350
351
352
353
  else
    {
      // An user wants to join an invalid IRC channel, return a presence error to him
      if (type.empty())
354
        this->send_invalid_room_error(to.local, to.resource, from);
355
    }
356
  stanza_error.disable();
357
358
}

louiz’'s avatar
louiz’ committed
359
360
void XmppComponent::handle_message(const Stanza& stanza)
{
361
362
363
364
365
  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");

366
367
  if (from.empty())
    return;
368
  if (type.empty())
369
    type = "normal";
370
371
372
  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);
  Iid iid(to.local);
373
374
375

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
376
  utils::ScopeGuard stanza_error([&](){
377
      this->send_stanza_error("message", from, to_str, id,
378
                              error_type, error_name, "");
379
    });
380
  XmlNode* body = stanza.get_child("body", COMPONENT_NS);
louiz’'s avatar
louiz’ committed
381
  if (type == "groupchat" && iid.is_channel)
louiz’'s avatar
louiz’ committed
382
    {
383
384
      if (body && !body->get_inner().empty())
        {
louiz’'s avatar
louiz’ committed
385
          bridge->send_channel_message(iid, body->get_inner());
386
        }
387
      XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
388
389
      if (subject)
        bridge->set_channel_topic(iid, subject->get_inner());
louiz’'s avatar
louiz’ committed
390
    }
391
392
  else if (type == "error")
    {
393
      const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
394
395
396
397
398
      // Only a set of errors are considered “fatal”. If we encounter one of
      // them, we purge (we disconnect the user from all the IRC servers).
      // We consider this to be true, unless the error condition is
      // specified and is not in the kickable_errors set
      bool kickable_error = true;
399
      if (error && error->has_children())
400
401
402
        {
          const XmlNode* condition = error->get_last_child();
          if (kickable_errors.find(condition->get_name()) == kickable_errors.end())
403
            kickable_error = false;
404
405
406
407
        }
      if (kickable_error)
        bridge->shutdown("Error from remote client");
    }
408
  else if (type == "chat")
louiz’'s avatar
louiz’ committed
409
410
    {
      if (body && !body->get_inner().empty())
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
        {
          // a message for nick!server
          if (iid.is_user && !iid.get_local().empty())
            {
              bridge->send_private_message(iid, body->get_inner());
              bridge->remove_preferred_from_jid(iid.get_local());
            }
          else if (!iid.is_user && !to.resource.empty())
            { // a message for chan%server@biboumi/Nick or
              // server@biboumi/Nick
              // Convert that into a message to nick!server
              Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server());
              bridge->send_private_message(user_iid, body->get_inner());
              bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
            }
        }
louiz’'s avatar
louiz’ committed
427
    }
louiz’'s avatar
louiz’ committed
428
429
  else if (iid.is_user)
    this->send_invalid_user_error(to.local, from);
430
  stanza_error.disable();
louiz’'s avatar
louiz’ committed
431
432
}

433
434
435
436
437
438
439
440
441
442
443
444
// We MUST return an iq, whatever happens, except if the type is
// "result".
// To do this, we use a scopeguard. If an exception is raised somewhere, an
// iq of type error "internal-server-error" is sent. If we handle the
// request properly (by calling a function that registers an iq to be sent
// later, or that directly sends an iq), we disable the ScopeGuard. If we
// reach the end of the function without having disabled the scopeguard, we
// send a "feature-not-implemented" iq as a result.  If an other kind of
// error is found (for example the feature is implemented in biboumi, but
// the request is missing some attribute) we can just change the values of
// error_type and error_name and return from the function (without disabling
// the scopeguard); an iq error will be sent
445
446
void XmppComponent::handle_iq(const Stanza& stanza)
{
447
448
449
450
451
  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");

452
453
  if (from.empty())
    return;
454
455
  if (id.empty() || to_str.empty() || type.empty())
    {
456
      this->send_stanza_error("iq", from, this->served_hostname, id,
457
                              "modify", "bad-request", "");
458
459
460
461
      return;
    }

  Bridge* bridge = this->get_user_bridge(from);
462
  Jid to(to_str);
463

464
465
  // These two values will be used in the error iq sent if we don't disable
  // the scopeguard.
466
467
  std::string error_type("cancel");
  std::string error_name("internal-server-error");
468
  utils::ScopeGuard stanza_error([&](){
469
      this->send_stanza_error("iq", from, to_str, id,
470
                              error_type, error_name, "");
471
    });
472
473
474
  if (type == "set")
    {
      XmlNode* query;
475
      if ((query = stanza.get_child("query", MUC_ADMIN_NS)))
476
        {
477
          const XmlNode* child = query->get_child("item", MUC_ADMIN_NS);
478
          if (child)
479
            {
480
481
              std::string nick = child->get_tag("nick");
              std::string role = child->get_tag("role");
482
              if (!nick.empty() && role == "none")
483
                {               // This is a kick
484
                  std::string reason;
485
                  XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
486
487
488
                  if (reason_el)
                    reason = reason_el->get_inner();
                  Iid iid(to.local);
489
                  bridge->send_irc_kick(iid, nick, reason, id, from);
490
                  stanza_error.disable();
491
                }
492
493
            }
        }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
      else if ((query = stanza.get_child("command", ADHOC_NS)))
        {
          Stanza response("iq");
          response["to"] = from;
          response["from"] = this->served_hostname;
          response["id"] = id;
          XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query);
          if (inner_node.get_child("error", ADHOC_NS))
            response["type"] = "error";
          else
            response["type"] = "result";
          response.add_child(std::move(inner_node));
          response.close();
          this->send_stanza(response);
          stanza_error.disable();
        }
510
    }
511
512
513
  else if (type == "get")
    {
      XmlNode* query;
514
      if ((query = stanza.get_child("query", DISCO_INFO_NS)))
515
516
        { // Disco info
          if (to_str == this->served_hostname)
517
518
519
520
521
522
523
524
525
526
            {
              const std::string node = query->get_tag("node");
              if (node.empty())
                {
                  // On the gateway itself
                  this->send_self_disco_info(id, from);
                  stanza_error.disable();
                }
            }
        }
527
528
529
      else if ((query = stanza.get_child("query", VERSION_NS)))
        {
          Iid iid(to.local);
530
531
532
533
534
535
536
537
538
539
540
541
542
          if (iid.is_user ||
              (iid.is_channel && !to.resource.empty()))
            {
              // Get the IRC user version
              std::string target;
              if (iid.is_user)
                target = iid.get_local();
              else
                target = to.resource;
              bridge->send_irc_version_request(iid.get_server(), target, id,
                                               from, to_str);
            }
          else
543
544
            {
              // On the gateway itself or on a channel
545
              this->send_version(id, from, to_str);
546
547
548
            }
          stanza_error.disable();
        }
549
550
551
552
553
554
      else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
        {
          const std::string node = query->get_tag("node");
          if (node == ADHOC_NS)
            {
              this->send_adhoc_commands_list(id, from);
555
              stanza_error.disable();
556
557
558
            }
        }
    }
559
  else if (type == "result")
560
    {
561
      stanza_error.disable();
louiz’'s avatar
louiz’ committed
562
      XmlNode* query;
563
      if ((query = stanza.get_child("query", VERSION_NS)))
louiz’'s avatar
louiz’ committed
564
        {
565
566
567
          XmlNode* name_node = query->get_child("name", VERSION_NS);
          XmlNode* version_node = query->get_child("version", VERSION_NS);
          XmlNode* os_node = query->get_child("os", VERSION_NS);
louiz’'s avatar
louiz’ committed
568
569
570
571
572
573
574
575
576
577
578
579
          std::string name;
          std::string version;
          std::string os;
          if (name_node)
            name = name_node->get_inner() + " (through the biboumi gateway)";
          if (version_node)
            version = version_node->get_inner();
          if (os_node)
            os = os_node->get_inner();
          const Iid iid(to.local);
          bridge->send_xmpp_version_to_irc(iid, name, version, os);
        }
580
    }
581
582
  error_type = "cancel";
  error_name = "feature-not-implemented";
583
584
}

louiz’'s avatar
louiz’ committed
585
586
void XmppComponent::handle_error(const Stanza& stanza)
{
587
  XmlNode* text = stanza.get_child("text", STREAMS_NS);
louiz’'s avatar
louiz’ committed
588
589
590
591
  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
592
593
594
595
596
#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
597
598
}

599
600
601
602
603
604
605
606
607
608
609
610
611
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();
    }
}

612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
Bridge* XmppComponent::find_user_bridge(const std::string& user_jid)
{
  try
    {
      return this->bridges.at(user_jid).get();
    }
  catch (const std::out_of_range& exception)
    {
      return nullptr;
    }
}

std::list<Bridge*> XmppComponent::get_bridges() const
{
  std::list<Bridge*> res;
  for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
    res.push_back(it->second.get());
  return res;
}

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

637
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid)
638
639
640
{
  XmlNode node("message");
  node["to"] = to;
641
642
643
644
  if (fulljid)
    node["from"] = from;
  else
    node["from"] = from + "@" + this->served_hostname;
645
646
  if (!type.empty())
    node["type"] = type;
647
  XmlNode body_node("body");
648
  body_node.set_inner(std::get<0>(body));
649
650
  body_node.close();
  node.add_child(std::move(body_node));
651
652
653
654
655
656
657
658
659
  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));
    }
660
661
662
663
  node.close();
  this->send_stanza(node);
}

664
665
666
void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& nick,
                                   const std::string& realjid,
667
668
669
670
                                   const std::string& affiliation,
                                   const std::string& role,
                                   const std::string& to,
                                   const bool self)
671
672
673
674
675
676
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
677
  x["xmlns"] = MUC_USER_NS;
678
679

  XmlNode item("item");
680
681
682
683
  if (!affiliation.empty())
    item["affiliation"] = affiliation;
  if (!role.empty())
    item["role"] = role;
684
685
686
687
688
689
  if (!realjid.empty())
    {
      const std::string preped_jid = jidprep(realjid);
      if (!preped_jid.empty())
        item["jid"] = preped_jid;
    }
690
  item.close();
691
  x.add_child(std::move(item));
692

693
694
695
696
697
698
699
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      status.close();
      x.add_child(std::move(status));
    }
700
701
702
703
704
705
  x.close();
  node.add_child(std::move(x));
  node.close();
  this->send_stanza(node);
}

706
707
708
709
710
void XmppComponent::send_invalid_room_error(const std::string& muc_name,
                                            const std::string& nick,
                                            const std::string& to)
{
  Stanza presence("presence");
711
712
713
714
  if (!muc_name.empty())
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  else
    presence["from"] = this->served_hostname;
715
716
717
718
719
720
721
722
  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
723
  error["type"] = "cancel";
louiz’'s avatar
louiz’ committed
724
725
726
727
  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));
728
729
730
731
732
733
734
735
736
737
738
739
740
741
  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);
}

louiz’'s avatar
louiz’ committed
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
void XmppComponent::send_invalid_user_error(const std::string& user_name, const std::string& to)
{
  Stanza message("message");
  message["from"] = user_name + "@" + this->served_hostname;
  message["to"] = to;
  message["type"] = "error";
  XmlNode x("x");
  x["xmlns"] = MUC_NS;
  x.close();
  message.add_child(std::move(x));
  XmlNode error("error");
  error["type"] = "cancel";
  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));
  XmlNode text("text");
  text["xmlns"] = STANZA_NS;
  text["xml:lang"] = "en";
  text.set_inner(user_name +
                 " is not a valid IRC user name. A correct user jid is of the form: <nick>!<server>@" +
                 this->served_hostname);
  text.close();
  error.add_child(std::move(text));
  error.close();
  message.add_child(std::move(error));
  message.close();
  this->send_stanza(message);
}

772
void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to)
773
774
775
776
777
778
{
  XmlNode message("message");
  message["to"] = to;
  message["from"] = from + "@" + this->served_hostname;
  message["type"] = "groupchat";
  XmlNode subject("subject");
779
  subject.set_inner(std::get<0>(topic));
780
781
782
783
784
  subject.close();
  message.add_child(std::move(subject));
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
785

786
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
787
788
789
{
  Stanza message("message");
  message["to"] = jid_to;
louiz’'s avatar
louiz’ committed
790
791
792
793
  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
794
795
  message["type"] = "groupchat";
  XmlNode body("body");
796
  body.set_inner(std::get<0>(xmpp_body));
louiz’'s avatar
louiz’ committed
797
798
  body.close();
  message.add_child(std::move(body));
799
800
801
802
803
804
805
806
807
  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
808
809
810
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
811

louiz’'s avatar
louiz’ committed
812
void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
louiz’'s avatar
louiz’ committed
813
814
815
816
817
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["type"] = "unavailable";
818
  const std::string message_str = std::get<0>(message);
louiz’'s avatar
louiz’ committed
819
820
821
822
823
824
825
826
827
828
829
830
  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
831
832
    {
      XmlNode status("status");
louiz’'s avatar
louiz’ committed
833
      status.set_inner(message_str);
louiz’'s avatar
louiz’ committed
834
835
836
837
838
839
      status.close();
      presence.add_child(std::move(status));
    }
  presence.close();
  this->send_stanza(presence);
}
louiz’'s avatar
louiz’ committed
840

841
842
843
844
845
846
847
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
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
{
  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);

875
  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
louiz’'s avatar
louiz’ committed
876
}
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912

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

914
void XmppComponent::send_presence_error(const std::string& muc_name,
915
916
917
918
919
920
                                        const std::string& nickname,
                                        const std::string& jid_to,
                                        const std::string& type,
                                        const std::string& condition,
                                        const std::string& error_code,
                                        const std::string& /* text */)
921
922
923
924
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
  presence["to"] = jid_to;
925
  presence["type"] = "error";
926
927
928
929
930
931
932
  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"] = type;
933
934
  if (!error_code.empty())
    error["code"] = error_code;
935
936
937
938
939
940
941
942
943
944
  XmlNode subnode(condition);
  subnode["xmlns"] = STANZA_NS;
  subnode.close();
  error.add_child(std::move(subnode));
  error.close();
  presence.add_child(std::move(error));
  presence.close();
  this->send_stanza(presence);
}

945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
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);
}
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981

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));
982
  for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
983
984
985
986
987
988
989
990
991
992
993
    {
      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
994

995
996
void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
                                 const std::string& version)
997
998
999
1000
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
For faster browsing, not all history is shown. View entire blame