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

4
#include <xmpp/xmpp_component.hpp>
5
#include <xmpp/jid.hpp>
6
7
8
9
10
11
12
13

#include <iostream>

// CryptoPP
#include <filters.h>
#include <hex.h>
#include <sha.h>

14
15
16
17
#define STREAM_NS        "http://etherx.jabber.org/streams"
#define COMPONENT_NS     "jabber:component:accept"
#define MUC_NS           "http://jabber.org/protocol/muc"
#define MUC_USER_NS      MUC_NS"#user"
18
#define MUC_ADMIN_NS     MUC_NS"#admin"
19
20
21
#define DISCO_NS         "http://jabber.org/protocol/disco"
#define DISCO_ITEMS_NS   DISCO_NS"#items"
#define DISCO_INFO_NS    DISCO_NS"#info"
22
#define XHTMLIM_NS       "http://jabber.org/protocol/xhtml-im"
23

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

XmppComponent::~XmppComponent()
{
}

void XmppComponent::start()
{
51
  this->connect("127.0.0.1", "5347");
52
53
54
55
}

void XmppComponent::send_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
56
57
58
  std::string str = stanza.to_string();
  log_debug("XMPP SENDING: " << str);
  this->send_data(std::move(str));
59
60
61
62
}

void XmppComponent::on_connected()
{
louiz’'s avatar
louiz’ committed
63
  log_info("connected to XMPP server");
64
  XmlNode node("stream:stream", nullptr);
65
66
  node["xmlns"] = COMPONENT_NS;
  node["xmlns:stream"] = STREAM_NS;
67
  node["to"] = this->served_hostname;
68
69
70
71
72
  this->send_stanza(node);
}

void XmppComponent::on_connection_close()
{
louiz’'s avatar
louiz’ committed
73
  log_info("XMPP server closed connection");
74
75
76
77
}

void XmppComponent::parse_in_buffer()
{
78
  this->parser.feed(this->in_buf.data(), this->in_buf.size(), false);
79
80
81
82
83
  this->in_buf.clear();
}

void XmppComponent::on_remote_stream_open(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
84
  log_debug("XMPP DOCUMENT OPEN: " << node.to_string());
85
86
87
88
89
90
  try
    {
      this->stream_id = node["id"];
    }
  catch (const AttributeNotFound& e)
    {
louiz’'s avatar
louiz’ committed
91
      log_error("Error: no attribute 'id' found");
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
      this->send_stream_error("bad-format", "missing 'id' attribute");
      this->close_document();
      return ;
    }

  // Try to authenticate
  CryptoPP::SHA1 sha1;
  std::string digest;
  CryptoPP::StringSource foo(this->stream_id + this->secret, true,
                      new CryptoPP::HashFilter(sha1,
                          new CryptoPP::HexEncoder(
                              new CryptoPP::StringSink(digest), false)));
  Stanza handshake("handshake", nullptr);
  handshake.set_inner(digest);
  handshake.close();
  this->send_stanza(handshake);
}

void XmppComponent::on_remote_stream_close(const XmlNode& node)
{
louiz’'s avatar
louiz’ committed
112
  log_debug("XMPP DOCUMENT CLOSE " << node.to_string());
113
114
115
116
}

void XmppComponent::on_stanza(const Stanza& stanza)
{
louiz’'s avatar
louiz’ committed
117
  log_debug("XMPP RECEIVING: " << stanza.to_string());
118
  std::function<void(const Stanza&)> handler;
119
120
  try
    {
121
      handler = this->stanza_handlers.at(stanza.get_name());
122
123
124
    }
  catch (const std::out_of_range& exception)
    {
louiz’'s avatar
louiz’ committed
125
      log_warning("No handler for stanza of type " << stanza.get_name());
126
127
      return;
    }
128
  handler(stanza);
129
130
131
132
133
134
}

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
  XmlNode node("stream:error", nullptr);
  XmlNode error(name, nullptr);
135
  error["xmlns"] = STREAM_NS;
136
137
138
139
140
141
142
143
144
145
  if (!explanation.empty())
    error.set_inner(explanation);
  error.close();
  node.add_child(std::move(error));
  node.close();
  this->send_stanza(node);
}

void XmppComponent::close_document()
{
louiz’'s avatar
louiz’ committed
146
  log_debug("XMPP SENDING: </stream:stream>");
147
148
149
150
151
  this->send_data("</stream:stream>");
}

void XmppComponent::handle_handshake(const Stanza& stanza)
{
152
  (void)stanza;
153
154
  this->authenticated = true;
}
155
156
157
158
159
160

void XmppComponent::handle_presence(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  Iid iid(to.local);
louiz’'s avatar
louiz’ committed
161
162
163
164
165
166
167
168
169
  std::string type;
  try {
    type = stanza["type"];
  }
  catch (const AttributeNotFound&) {}

  if (!iid.chan.empty() && !iid.chan.empty())
    { // presence toward a MUC that corresponds to an irc channel
      if (type.empty())
louiz’'s avatar
louiz’ committed
170
171
172
173
174
175
176
        {
          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);
          else
            bridge->join_irc_channel(iid, to.resource);
        }
louiz’'s avatar
louiz’ committed
177
178
      else if (type == "unavailable")
        {
179
          XmlNode* status = stanza.get_child(MUC_USER_NS":status");
louiz’'s avatar
louiz’ committed
180
181
182
          bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
        }
    }
183
184
}

louiz’'s avatar
louiz’ committed
185
186
187
188
189
void XmppComponent::handle_message(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  Iid iid(to.local);
190
  XmlNode* body = stanza.get_child(COMPONENT_NS":body");
louiz’'s avatar
louiz’ committed
191
192
193
194
195
196
  if (stanza["type"] == "groupchat")
    {
      if (to.resource.empty())
        if (body && !body->get_inner().empty())
          bridge->send_channel_message(iid, body->get_inner());
    }
louiz’'s avatar
louiz’ committed
197
198
199
200
201
  else
    {
      if (body && !body->get_inner().empty())
        bridge->send_private_message(iid, body->get_inner());
    }
louiz’'s avatar
louiz’ committed
202
203
}

204
205
206
207
208
209
210
211
212
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
void XmppComponent::handle_iq(const Stanza& stanza)
{
  Bridge* bridge = this->get_user_bridge(stanza["from"]);
  Jid to(stanza["to"]);
  std::string type;
  try {
    type = stanza["type"];
  }
  catch (const AttributeNotFound&)
    { return; }
  if (type == "set")
    {
      XmlNode* query;
      if ((query = stanza.get_child(MUC_ADMIN_NS":query")))
        {
          XmlNode* child;
          if ((child = query->get_child(MUC_ADMIN_NS":item")))
            {
              std::string nick;
              std::string role;
              try {
                nick = (*child)["nick"];
                role = (*child)["role"];
              }
              catch (const AttributeNotFound&)
                { return; }
              if (!nick.empty() && role == "none")
                {
                  std::string reason;
                  XmlNode* reason_el = child->get_child(MUC_ADMIN_NS":reason");
                  if (reason_el)
                    reason = reason_el->get_inner();
                  Iid iid(to.local);
                  bridge->send_irc_kick(iid, nick, reason);
                }
            }
        }
    }
}

244
245
246
247
248
249
250
251
252
253
254
255
256
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();
    }
}

257
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to)
258
259
260
261
262
{
  XmlNode node("message");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname;
  XmlNode body_node("body");
263
  body_node.set_inner(std::get<0>(body));
264
265
266
267
268
269
  body_node.close();
  node.add_child(std::move(body_node));
  node.close();
  this->send_stanza(node);
}

270
271
272
273
void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& nick,
                                   const std::string& realjid,
                                   const std::string& to)
274
275
276
277
278
279
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
280
  x["xmlns"] = MUC_USER_NS;
281
282
283
284
285

  // TODO: put real values here
  XmlNode item("item");
  item["affiliation"] = "member";
  item["role"] = "participant";
286
287
288
289
290
291
  if (!realjid.empty())
    {
      const std::string preped_jid = jidprep(realjid);
      if (!preped_jid.empty())
        item["jid"] = preped_jid;
    }
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
  item.close();
  x.add_child(std::move(item));
  x.close();
  node.add_child(std::move(x));
  node.close();
  this->send_stanza(node);
}

void XmppComponent::send_self_join(const std::string& from, const std::string& nick, const std::string& to)
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
307
  x["xmlns"] = MUC_USER_NS;
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327

  // TODO: put real values here
  XmlNode item("item");
  item["affiliation"] = "member";
  item["role"] = "participant";
  item.close();
  x.add_child(std::move(item));

  XmlNode status("status");
  status["code"] = "110";
  status.close();
  x.add_child(std::move(status));

  x.close();

  node.add_child(std::move(x));
  node.close();
  this->send_stanza(node);
}

328
void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to)
329
330
331
332
333
334
{
  XmlNode message("message");
  message["to"] = to;
  message["from"] = from + "@" + this->served_hostname;
  message["type"] = "groupchat";
  XmlNode subject("subject");
335
  subject.set_inner(std::get<0>(topic));
336
337
338
339
340
  subject.close();
  message.add_child(std::move(subject));
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
341

342
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
343
344
345
{
  Stanza message("message");
  message["to"] = jid_to;
louiz’'s avatar
louiz’ committed
346
347
348
349
  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
350
351
  message["type"] = "groupchat";
  XmlNode body("body");
352
  body.set_inner(std::get<0>(xmpp_body));
louiz’'s avatar
louiz’ committed
353
354
  body.close();
  message.add_child(std::move(body));
355
356
357
358
359
360
361
362
363
  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
364
365
366
  message.close();
  this->send_stanza(message);
}
louiz’'s avatar
louiz’ committed
367

368
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
369
370
371
372
373
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["type"] = "unavailable";
374
375
  const std::string message_str = std::get<0>(message);
  if (message_str.empty() || self)
louiz’'s avatar
louiz’ committed
376
377
    {
      XmlNode status("status");
378
379
      if (!message_str.empty())
        status.set_inner(message_str);
louiz’'s avatar
louiz’ committed
380
381
382
383
384
385
386
387
      if (self)
        status["code"] = "110";
      status.close();
      presence.add_child(std::move(status));
    }
  presence.close();
  this->send_stanza(presence);
}
louiz’'s avatar
louiz’ committed
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419

void XmppComponent::send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self)
{
  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);

  if (self)
    this->send_self_join(muc_name, new_nick, jid_to);
  else
420
    this->send_user_join(muc_name, new_nick, "", jid_to);
louiz’'s avatar
louiz’ committed
421
}
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457

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