irc_client.cpp 30.2 KB
Newer Older
1
#include <utils/timed_events.hpp>
2
#include <database/database.hpp>
3
#include <irc/irc_message.hpp>
4
5
6
7
#include <irc/irc_client.hpp>
#include <bridge/bridge.hpp>
#include <irc/irc_user.hpp>

louiz’'s avatar
louiz’ committed
8
#include <logger/logger.hpp>
9
#include <config/config.hpp>
louiz’'s avatar
louiz’ committed
10
#include <utils/tolower.hpp>
11
#include <utils/split.hpp>
12

13
#include <sstream>
14
15
16
#include <iostream>
#include <stdexcept>

17
#include <chrono>
louiz’'s avatar
louiz’ committed
18
#include <string>
19

20
#include "biboumi.h"
21
#include "louloulibs.h"
louiz’'s avatar
louiz’ committed
22

louiz’'s avatar
louiz’ committed
23
using namespace std::string_literals;
24
using namespace std::chrono_literals;
louiz’'s avatar
louiz’ committed
25

26

27
28
29
IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
                     const std::string& nickname, const std::string& username,
                     const std::string& realname, Bridge* bridge):
30
  TCPSocketHandler(poller),
31
32
  hostname(hostname),
  username(username),
33
34
  realname(realname),
  current_nick(nickname),
louiz’'s avatar
louiz’ committed
35
  bridge(bridge),
louiz’'s avatar
louiz’ committed
36
  welcomed(false),
37
38
  chanmodes({"", "", "", ""}),
  chantypes({'#', '&'})
39
{
40
41
42
43
44
45
46
47
  this->dummy_channel.topic = "This is a virtual channel provided for "
                              "convenience by biboumi, it is not connected "
                              "to any actual IRC channel of the server '" + this->hostname +
                              "', and sending messages in it has no effect. "
                              "Its main goal is to keep the connection to the IRC server "
                              "alive without having to join a real channel of that server. "
                              "To disconnect from the IRC server, leave this room and all "
                              "other IRC channels of that server.";
48
49
50
51
52
53
54
55
56
57
58
59
60
#ifdef USE_DATABASE
  auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(),
                                                  this->get_hostname());
  std::vector<std::string> ports = utils::split(options.ports, ';', false);
  for (auto it = ports.rbegin(); it != ports.rend(); ++it)
    this->ports_to_try.emplace(*it, false);
# ifdef BOTAN_FOUND
  ports = utils::split(options.tlsPorts, ';', false);
  for (auto it = ports.rbegin(); it != ports.rend(); ++it)
    this->ports_to_try.emplace(*it, true);
# endif // BOTAN_FOUND

#else  // not USE_DATABASE
louiz’'s avatar
louiz’ committed
61
  this->ports_to_try.emplace("6667", false); // standard non-encrypted port
62
# ifdef BOTAN_FOUND
louiz’'s avatar
louiz’ committed
63
64
  this->ports_to_try.emplace("6670", true);  // non-standard but I want it for some servers
  this->ports_to_try.emplace("6697", true);  // standard encrypted port
65
66
# endif // BOTAN_FOUND
#endif // USE_DATABASE
67
68
69
70
}

IrcClient::~IrcClient()
{
71
72
73
  // This event may or may not exist (if we never got connected, it
  // doesn't), but it's ok
  TimedEventsManager::instance().cancel("PING"s + this->hostname + this->bridge->get_jid());
74
75
}

76
77
void IrcClient::start()
{
78
79
  if (this->is_connecting() || this->is_connected())
    return;
louiz’'s avatar
louiz’ committed
80
81
82
83
  std::string port;
  bool tls;
  std::tie(port, tls) = this->ports_to_try.top();
  this->ports_to_try.pop();
louiz’'s avatar
louiz’ committed
84
  this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s +
louiz’'s avatar
louiz’ committed
85
86
87
                                  this->hostname + ":" + port + " (" +
                                  (tls ? "encrypted" : "not encrypted") + ")");
  this->connect(this->hostname, port, tls);
88
89
90
91
92
}

void IrcClient::on_connection_failed(const std::string& reason)
{
  this->bridge->send_xmpp_message(this->hostname, "",
louiz’'s avatar
louiz’ committed
93
                                  "Connection failed: "s + reason);
94
95
96
97
98

  if (this->hostname_resolution_failed)
    while (!this->ports_to_try.empty())
      this->ports_to_try.pop();

louiz’'s avatar
louiz’ committed
99
  if (this->ports_to_try.empty())
100
    {
louiz’'s avatar
louiz’ committed
101
      // Send an error message for all room that the user wanted to join
102
      for (const auto& tuple: this->channels_to_join)
louiz’'s avatar
louiz’ committed
103
        {
104
          Iid iid(std::get<0>(tuple) + "%" + this->hostname);
105
106
107
          this->bridge->send_presence_error(iid, this->current_nick,
                                            "cancel", "item-not-found",
                                            "", reason);
louiz’'s avatar
louiz’ committed
108
        }
109
    }
louiz’'s avatar
louiz’ committed
110
111
  else                          // try the next port
    this->start();
112
113
}

louiz’'s avatar
louiz’ committed
114
115
void IrcClient::on_connected()
{
116
117
118
119
120
121
#ifdef USE_DATABASE
  auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(),
                                                  this->get_hostname());
  if (!options.pass.value().empty())
    this->send_pass_command(options.pass.value());
#endif
122

123
  this->send_nick_command(this->username);
124

125
#ifdef USE_DATABASE
126
127
128
129
130
131
132
133
  if (Config::get("realname_customization", "true") == "true")
    {
      if (!options.username.value().empty())
        this->username = options.username.value();
      if (!options.realname.value().empty())
        this->realname = options.realname.value();
      this->send_user_command(username, realname);
    }
134
135
136
#endif
  this->send_user_command(this->username, this->realname);

louiz’'s avatar
louiz’ committed
137
  this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
138
  this->send_pending_data();
louiz’'s avatar
louiz’ committed
139
140
}

141
void IrcClient::on_connection_close(const std::string& error_msg)
142
{
143
  std::string message = "Connection closed";
144
145
  if (!error_msg.empty())
    message += ": " + error_msg;
146
147
  else
    message += ".";
148
149
  const IrcMessage error{"ERROR", {message}};
  this->on_error(error);
150
  log_warning(message);
151
152
}

louiz’'s avatar
louiz’ committed
153
IrcChannel* IrcClient::get_channel(const std::string& n)
154
{
louiz’'s avatar
louiz’ committed
155
  if (n.empty())
156
    return &this->dummy_channel;
louiz’'s avatar
louiz’ committed
157
  const std::string name = utils::tolower(n);
158
159
160
161
162
163
164
165
166
167
168
  try
    {
      return this->channels.at(name).get();
    }
  catch (const std::out_of_range& exception)
    {
      this->channels.emplace(name, std::make_unique<IrcChannel>());
      return this->channels.at(name).get();
    }
}

louiz’'s avatar
louiz’ committed
169
170
bool IrcClient::is_channel_joined(const std::string& name)
{
louiz’'s avatar
louiz’ committed
171
172
  IrcChannel* channel = this->get_channel(name);
  return channel->joined;
louiz’'s avatar
louiz’ committed
173
174
}

louiz’'s avatar
louiz’ committed
175
176
177
178
179
std::string IrcClient::get_own_nick() const
{
  return this->current_nick;
}

180
void IrcClient::parse_in_buffer(const size_t)
181
{
louiz’'s avatar
louiz’ committed
182
183
184
185
186
187
188
  while (true)
    {
      auto pos = this->in_buf.find("\r\n");
      if (pos == std::string::npos)
        break ;
      IrcMessage message(this->in_buf.substr(0, pos));
      this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
189
      log_debug("IRC RECEIVING: (" << this->get_hostname() << ") " << message);
190
191
192

      // Call the standard callback (if any), associated with the command
      // name that we just received.
193
194
      auto it = irc_callbacks.find(message.command);
      if (it != irc_callbacks.end())
195
        {
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
          const auto& limits = it->second.second;
          // Check that the Message is well formed before actually calling
          // the callback. limits.first is the min number of arguments,
          // second is the max
          if (message.arguments.size() < limits.first ||
              (limits.second > 0 && message.arguments.size() > limits.second))
            log_warning("Invalid number of arguments for IRC command “" << message.command <<
                        "”: " << message.arguments.size());
          else
            {
              const auto& cb = it->second.first;
              try {
                (this->*(cb))(message);
              } catch (const std::exception& e) {
                log_error("Unhandled exception: " << e.what());
              }
            }
213
        }
214
      else
215
216
217
218
219
        {
          log_info("No handler for command " << message.command <<
                   ", forwarding the arguments to the user");
          this->on_unknown_message(message);
        }
220
      // Try to find a waiting_iq, which response will be triggered by this IrcMessage
221
      this->bridge->trigger_on_irc_message(this->hostname, message);
louiz’'s avatar
louiz’ committed
222
223
224
225
226
    }
}

void IrcClient::send_message(IrcMessage&& message)
{
227
  log_debug("IRC SENDING: (" << this->get_hostname() << ") " << message);
louiz’'s avatar
louiz’ committed
228
229
230
231
232
233
  std::string res;
  if (!message.prefix.empty())
    res += ":" + std::move(message.prefix) + " ";
  res += std::move(message.command);
  for (const std::string& arg: message.arguments)
    {
louiz’'s avatar
:3    
louiz’ committed
234
      if (arg.find(" ") != std::string::npos ||
louiz’'s avatar
louiz’ committed
235
          (!arg.empty() && arg[0] == ':'))
louiz’'s avatar
louiz’ committed
236
237
238
239
240
241
242
        {
          res += " :" + arg;
          break;
        }
      res += " " + arg;
    }
  res += "\r\n";
243
  this->send_data(std::move(res));
louiz’'s avatar
louiz’ committed
244
245
}

louiz’'s avatar
louiz’ committed
246
247
void IrcClient::send_raw(const std::string& txt)
{
248
  log_debug("IRC SENDING (raw): (" << this->get_hostname() << ") " << txt);
louiz’'s avatar
louiz’ committed
249
250
251
  this->send_data(txt + "\r\n");
}

louiz’'s avatar
louiz’ committed
252
253
void IrcClient::send_user_command(const std::string& username, const std::string& realname)
{
louiz’'s avatar
louiz’ committed
254
  this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname}));
louiz’'s avatar
louiz’ committed
255
256
257
258
259
260
261
}

void IrcClient::send_nick_command(const std::string& nick)
{
  this->send_message(IrcMessage("NICK", {nick}));
}

262
263
264
265
266
void IrcClient::send_pass_command(const std::string& password)
{
  this->send_message(IrcMessage("PASS", {password}));
}

267
268
269
270
271
void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason)
{
  this->send_message(IrcMessage("KICK", {chan_name, target, reason}));
}

272
273
274
275
276
void IrcClient::send_list_command()
{
  this->send_message(IrcMessage("LIST", {}));
}

277
278
279
280
281
void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
{
  this->send_message(IrcMessage("TOPIC", {chan_name, topic}));
}

282
void IrcClient::send_quit_command(const std::string& reason)
louiz’'s avatar
louiz’ committed
283
{
284
  this->send_message(IrcMessage("QUIT", {reason}));
louiz’'s avatar
louiz’ committed
285
286
}

287
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
louiz’'s avatar
louiz’ committed
288
{
louiz’'s avatar
louiz’ committed
289
  if (this->welcomed == false)
290
    this->channels_to_join.emplace_back(chan_name, password);
291
292
  else if (password.empty())
    this->send_message(IrcMessage("JOIN", {chan_name}));
louiz’'s avatar
louiz’ committed
293
  else
294
    this->send_message(IrcMessage("JOIN", {chan_name, password}));
295
  this->start();
296
297
}

louiz’'s avatar
louiz’ committed
298
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
299
{
louiz’'s avatar
louiz’ committed
300
301
302
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == false)
    {
louiz’'s avatar
louiz’ committed
303
      log_warning("Cannot send message to channel " << chan_name << ", it is not joined");
louiz’'s avatar
louiz’ committed
304
305
      return false;
    }
306
307
308
309
310
311
312
313
314
  // Cut the message body into 400-bytes parts (because the whole command
  // must fit into 512 bytes, that's an easy way to make sure the chan name
  // + body fits. I’m lazy.)
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
      this->send_message(IrcMessage("PRIVMSG", {chan_name, body.substr(pos, 400)}));
      pos += 400;
    }
louiz’'s avatar
louiz’ committed
315
316
317
  return true;
}

louiz’'s avatar
louiz’ committed
318
void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
319
{
320
321
322
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
louiz’'s avatar
louiz’ committed
323
      this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)}));
324
325
      pos += 400;
    }
326
327
328
  // We always try to insert and we don't care if the username was already
  // in the set.
  this->nicks_to_treat_as_private.insert(username);
louiz’'s avatar
louiz’ committed
329
330
}

louiz’'s avatar
louiz’ committed
331
332
333
334
void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
{
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == true)
louiz’'s avatar
louiz’ committed
335
336
    {
      if (chan_name.empty())
louiz’'s avatar
louiz’ committed
337
        this->leave_dummy_channel(status_message);
louiz’'s avatar
louiz’ committed
338
339
340
      else
        this->send_message(IrcMessage("PART", {chan_name, status_message}));
    }
louiz’'s avatar
louiz’ committed
341
342
343
344
345
346
347
348
}

void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments)
{
  std::vector<std::string> args(arguments);
  args.insert(args.begin(), chan_name);
  IrcMessage m("MODE", std::move(args));
  this->send_message(std::move(m));
louiz’'s avatar
louiz’ committed
349
350
}

louiz’'s avatar
louiz’ committed
351
352
353
354
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
355
356
}

louiz’'s avatar
louiz’ committed
357
void IrcClient::on_pong(const IrcMessage&)
358
359
360
{
}

361
362
363
364
365
void IrcClient::send_ping_command()
{
  this->send_message(IrcMessage("PING", {"biboumi"}));
}

366
367
368
369
370
371
372
void IrcClient::forward_server_message(const IrcMessage& message)
{
  const std::string from = message.prefix;
  const std::string body = message.arguments[1];

  this->bridge->send_xmpp_message(this->hostname, from, body);
}
louiz’'s avatar
louiz’ committed
373

374
375
376
377
378
379
void IrcClient::on_notice(const IrcMessage& message)
{
  std::string from = message.prefix;
  const std::string to = message.arguments[0];
  const std::string body = message.arguments[1];

380
381
382
383
  if (!body.empty() && body[0] == '\01' && body[body.size() - 1] == '\01')
    // Do not forward the notice to the user if it's a CTCP command
    return ;

384
  if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end())
385
    {
louiz’'s avatar
louiz’ committed
386
      // The notice is for us precisely.
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401

      // Find out if we already sent a private message to this user. If yes
      // we treat that message as a private message coming from
      // it. Otherwise we treat it as a notice coming from the server.
      IrcUser user(from);
      std::string nick = utils::tolower(user.nick);
      if (this->nicks_to_treat_as_private.find(nick) !=
          this->nicks_to_treat_as_private.end())
        { // We previously sent a message to that nick)
          this->bridge->send_message({nick + "!" + this->hostname}, nick, body,
                                     false);
        }
      else
        this->bridge->send_xmpp_message(this->hostname, from, body);
    }
402
403
  else
    {
404
405
406
      // The notice was directed at a channel we are in. Modify the message
      // to indicate that it is a notice, and make it a MUC message coming
      // from the MUC JID
louiz’'s avatar
louiz’ committed
407
      IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 "s + body});
408
409
410
411
      this->on_channel_message(modified_message);
    }
}

louiz’'s avatar
louiz’ committed
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
void IrcClient::on_isupport_message(const IrcMessage& message)
{
  const size_t len = message.arguments.size();
  for (size_t i = 1; i < len; ++i)
  {
    const std::string token = message.arguments[i];
    if (token.substr(0, 10) == "CHANMODES=")
    {
      this->chanmodes = utils::split(token.substr(11), ',');
      // make sure we have 4 strings
      this->chanmodes.resize(4);
    }
    else if (token.substr(0, 7) == "PREFIX=")
      {
        size_t i = 8;           // jump PREFIX=(
        size_t j = 9;
        // Find the ) char
        while (j < token.size() && token[j] != ')')
          j++;
        j++;
        while (j < token.size() && token[i] != ')')
433
434
435
436
          {
            this->sorted_user_modes.push_back(token[i]);
            this->prefix_to_mode[token[j++]] = token[i++];
          }
louiz’'s avatar
louiz’ committed
437
      }
438
439
440
441
442
    else if (token.substr(0, 10) == "CHANTYPES=")
      {
        // Remove the default types, they apply only if no other value is
        // specified.
        this->chantypes.clear();
443
        size_t i = 10;
444
445
446
        while (i < token.size())
          this->chantypes.insert(token[i++]);
      }
louiz’'s avatar
louiz’ committed
447
448
  }
}
449

450
451
452
453
454
void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
{
  this->bridge->send_xmpp_message(this->hostname, from, message);
}

455
456
void IrcClient::set_and_forward_user_list(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
457
  const std::string chan_name = utils::tolower(message.arguments[2]);
458
459
460
461
  IrcChannel* channel = this->get_channel(chan_name);
  std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
  for (const std::string& nick: nicks)
    {
462
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
463
464
      if (user->nick != channel->get_self()->nick)
        {
465
466
467
          this->bridge->send_user_join(this->hostname, chan_name, user,
                                       user->get_most_significant_mode(this->sorted_user_modes),
                                       false);
468
469
470
471
472
        }
      else
        {
          // we now know the modes of self, so copy the modes into self
          channel->get_self()->modes = user->modes;
473
474
475
476
        }
    }
}

louiz’'s avatar
louiz’ committed
477
void IrcClient::on_channel_join(const IrcMessage& message)
478
{
louiz’'s avatar
louiz’ committed
479
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
480
481
482
483
484
  IrcChannel* channel;
  if (chan_name.empty())
    channel = &this->dummy_channel;
  else
    channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
485
486
  const std::string nick = message.prefix;
  if (channel->joined == false)
487
    channel->set_self(nick);
louiz’'s avatar
louiz’ committed
488
489
  else
    {
490
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
491
492
493
      this->bridge->send_user_join(this->hostname, chan_name, user,
                                   user->get_most_significant_mode(this->sorted_user_modes),
                                   false);
louiz’'s avatar
louiz’ committed
494
    }
495
496
}

louiz’'s avatar
louiz’ committed
497
498
499
500
501
void IrcClient::on_channel_message(const IrcMessage& message)
{
  const IrcUser user(message.prefix);
  const std::string nick = user.nick;
  Iid iid;
louiz’'s avatar
louiz’ committed
502
503
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
louiz’'s avatar
louiz’ committed
504
  const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
505
  bool muc = true;
louiz’'s avatar
louiz’ committed
506
  if (!this->get_channel(iid.get_local())->joined)
louiz’'s avatar
louiz’ committed
507
    {
louiz’'s avatar
louiz’ committed
508
509
      iid.is_user = true;
      iid.set_local(nick);
louiz’'s avatar
louiz’ committed
510
511
      muc = false;
    }
louiz’'s avatar
louiz’ committed
512
513
  else
    iid.is_channel = true;
514
515
516
517
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")
        this->bridge->send_message(iid, nick,
louiz’'s avatar
louiz’ committed
518
                  "/me"s + body.substr(7, body.size() - 8), muc);
519
520
      else if (body.substr(1, 8) == "VERSION\01")
        this->bridge->send_iq_version_request(nick, this->hostname);
521
      else if (body.substr(1, 5) == "PING ")
522
        this->bridge->send_xmpp_ping_request(utils::tolower(nick), this->hostname,
523
                                             body.substr(6, body.size() - 7));
524
525
526
    }
  else
    this->bridge->send_message(iid, nick, body, muc);
louiz’'s avatar
louiz’ committed
527
528
}

529
530
531
532
533
534
535
536
537
538
539
540
void IrcClient::on_rpl_liststart(const IrcMessage&)
{
}

void IrcClient::on_rpl_list(const IrcMessage&)
{
}

void IrcClient::on_rpl_listend(const IrcMessage&)
{
}

louiz’'s avatar
louiz’ committed
541
void IrcClient::empty_motd(const IrcMessage&)
542
543
544
545
546
547
548
549
550
551
552
553
554
{
  this->motd.erase();
}

void IrcClient::on_motd_line(const IrcMessage& message)
{
  const std::string body = message.arguments[1];
  // We could send the MOTD without a line break between each IRC-message,
  // but sometimes it contains some ASCII art, we use line breaks to keep
  // them intact.
  this->motd += body+"\n";
}

louiz’'s avatar
louiz’ committed
555
void IrcClient::send_motd(const IrcMessage&)
556
557
558
559
{
  this->bridge->send_xmpp_message(this->hostname, "", this->motd);
}

560
561
void IrcClient::on_topic_received(const IrcMessage& message)
{
562
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]);
563
  IrcChannel* channel = this->get_channel(chan_name);
564
  channel->topic = message.arguments[message.arguments.size() - 1];
louiz’'s avatar
louiz’ committed
565
566
  if (channel->joined)
    this->bridge->send_topic(this->hostname, chan_name, channel->topic);
567
568
569
570
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
571
  const std::string chan_name = utils::tolower(message.arguments[1]);
572
  IrcChannel* channel = this->get_channel(chan_name);
573
  channel->joined = true;
574
575
576
  this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(),
                               channel->get_self()->get_most_significant_mode(this->sorted_user_modes),
                               true);
577
  this->bridge->send_topic(this->hostname, chan_name, channel->topic);
578
}
louiz’'s avatar
louiz’ committed
579

580
581
582
583
584
585
586
void IrcClient::on_erroneous_nickname(const IrcMessage& message)
{
  const std::string error_msg = message.arguments.size() >= 3 ?
    message.arguments[2]: "Erroneous nickname";
  this->send_gateway_message(error_msg + ": " + message.arguments[1], message.prefix);
}

587
588
589
590
591
592
593
void IrcClient::on_nickname_conflict(const IrcMessage& message)
{
  const std::string nickname = message.arguments[1];
  this->on_generic_error(message);
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
  {
    Iid iid;
louiz’'s avatar
louiz’ committed
594
595
596
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
597
598
599
600
    this->bridge->send_nickname_conflict_error(iid, nickname);
  }
}

601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
{
  const std::string nickname = message.arguments[1];
  std::string txt;
  if (message.arguments.size() >= 3)
    txt = message.arguments[2];
  this->on_generic_error(message);
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
  {
    Iid iid;
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
    this->bridge->send_presence_error(iid, nickname,
                                      "cancel", "not-acceptable",
                                      "", txt);
  }
}

620
621
622
623
624
625
626
void IrcClient::on_generic_error(const IrcMessage& message)
{
  const std::string error_msg = message.arguments.size() >= 3 ?
    message.arguments[2]: "Unspecified error";
  this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix);
}

louiz’'s avatar
louiz’ committed
627
628
629
630
void IrcClient::on_welcome_message(const IrcMessage& message)
{
  this->current_nick = message.arguments[0];
  this->welcomed = true;
631
632
633
634
635
636
#ifdef USE_DATABASE
  auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(),
                                                  this->get_hostname());
  if (!options.afterConnectionCommand.value().empty())
    this->send_raw(options.afterConnectionCommand.value());
#endif
637
638
639
  // Install a repeated events to regularly send a PING
  TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
                                                      "PING"s + this->hostname + this->bridge->get_jid()));
640
641
  for (const auto& tuple: this->channels_to_join)
    this->send_join_command(std::get<0>(tuple), std::get<1>(tuple));
louiz’'s avatar
louiz’ committed
642
  this->channels_to_join.clear();
louiz’'s avatar
louiz’ committed
643
644
645
646
647
648
649
650
651
652
653
654
  // Indicate that the dummy channel is joined as well, if needed
  if (this->dummy_channel.joining)
    {
      // Simulate a message coming from the IRC server saying that we joined
      // the channel
      const IrcMessage join_message(this->get_nick(), "JOIN", {""});
      this->on_channel_join(join_message);
      const IrcMessage end_join_message(std::string(this->hostname), "366",
                                        {this->get_nick(),
                                            "", "End of NAMES list"});
      this->on_channel_completely_joined(end_join_message);
    }
louiz’'s avatar
louiz’ committed
655
}
louiz’'s avatar
louiz’ committed
656
657
658

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
659
  const std::string chan_name = message.arguments[0];
louiz’'s avatar
louiz’ committed
660
  IrcChannel* channel = this->get_channel(chan_name);
661
662
  if (!channel->joined)
    return ;
louiz’'s avatar
louiz’ committed
663
664
665
666
667
668
669
670
671
  std::string txt;
  if (message.arguments.size() >= 2)
    txt = message.arguments[1];
  const IrcUser* user = channel->find_user(message.prefix);
  if (user)
    {
      std::string nick = user->nick;
      channel->remove_user(user);
      Iid iid;
louiz’'s avatar
louiz’ committed
672
673
674
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.is_channel = true;
louiz’'s avatar
louiz’ committed
675
676
      bool self = channel->get_self()->nick == nick;
      if (self)
louiz’'s avatar
louiz’ committed
677
      {
louiz’'s avatar
louiz’ committed
678
        channel->joined = false;
louiz’'s avatar
louiz’ committed
679
        this->channels.erase(utils::tolower(chan_name));
louiz’'s avatar
louiz’ committed
680
681
682
        // channel pointer is now invalid
        channel = nullptr;
      }
683
      this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self);
louiz’'s avatar
louiz’ committed
684
685
    }
}
louiz’'s avatar
louiz’ committed
686

louiz’'s avatar
louiz’ committed
687
688
689
690
691
692
693
void IrcClient::on_error(const IrcMessage& message)
{
  const std::string leave_message = message.arguments[0];
  // The user is out of all the channels
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
  {
    Iid iid;
louiz’'s avatar
louiz’ committed
694
695
696
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
louiz’'s avatar
louiz’ committed
697
    IrcChannel* channel = it->second.get();
698
699
    if (!channel->joined)
      continue;
louiz’'s avatar
louiz’ committed
700
701
702
    std::string own_nick = channel->get_self()->nick;
    this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
  }
louiz’'s avatar
louiz’ committed
703
  this->channels.clear();
louiz’'s avatar
louiz’ committed
704
  this->send_gateway_message("ERROR: "s + leave_message);
louiz’'s avatar
louiz’ committed
705
706
}

louiz’'s avatar
louiz’ committed
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
void IrcClient::on_quit(const IrcMessage& message)
{
  std::string txt;
  if (message.arguments.size() >= 1)
    txt = message.arguments[0];
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
    {
      const std::string chan_name = it->first;
      IrcChannel* channel = it->second.get();
      const IrcUser* user = channel->find_user(message.prefix);
      if (user)
        {
          std::string nick = user->nick;
          channel->remove_user(user);
          Iid iid;
louiz’'s avatar
louiz’ committed
722
723
724
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
louiz’'s avatar
louiz’ committed
725
          this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
726
727
728
        }
    }
}
louiz’'s avatar
louiz’ committed
729
730
731
732
733
734
735
736
737
738
739
740
741

void IrcClient::on_nick(const IrcMessage& message)
{
  const std::string new_nick = message.arguments[0];
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
    {
      const std::string chan_name = it->first;
      IrcChannel* channel = it->second.get();
      IrcUser* user = channel->find_user(message.prefix);
      if (user)
        {
          std::string old_nick = user->nick;
          Iid iid;
louiz’'s avatar
louiz’ committed
742
743
744
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
745
746
747
          const bool self = channel->get_self()->nick == old_nick;
          const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
          this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);
louiz’'s avatar
louiz’ committed
748
749
750
751
752
753
754
755
756
757
          user->nick = new_nick;
          if (self)
            {
              channel->get_self()->nick = new_nick;
              this->current_nick = new_nick;
            }
        }
    }
}

758
759
void IrcClient::on_kick(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
760
  const std::string chan_name = utils::tolower(message.arguments[0]);
761
762
763
  const std::string target = message.arguments[1];
  const std::string reason = message.arguments[2];
  IrcChannel* channel = this->get_channel(chan_name);
764
765
  if (!channel->joined)
    return ;
766
767
768
769
  if (channel->get_self()->nick == target)
    channel->joined = false;
  IrcUser author(message.prefix);
  Iid iid;
louiz’'s avatar
louiz’ committed
770
771
772
  iid.set_local(chan_name);
  iid.set_server(this->hostname);
  iid.is_channel = true;
773
774
775
  this->bridge->kick_muc_user(std::move(iid), target, reason, author.nick);
}

louiz’'s avatar
louiz’ committed
776
777
778
void IrcClient::on_mode(const IrcMessage& message)
{
  const std::string target = message.arguments[0];
779
  if (this->chantypes.find(target[0]) != this->chantypes.end())
louiz’'s avatar
louiz’ committed
780
781
782
783
784
785
786
787
788
789
    this->on_channel_mode(message);
  else
    this->on_user_mode(message);
}

void IrcClient::on_channel_mode(const IrcMessage& message)
{
  // For now, just transmit the modes so the user can know what happens
  // TODO, actually interprete the mode.
  Iid iid;
louiz’'s avatar
louiz’ committed
790
791
792
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
  iid.is_channel = true;
louiz’'s avatar
louiz’ committed
793
  IrcUser user(message.prefix);
794
795
796
797
798
799
800
801
802
803
  std::string mode_arguments;
  for (size_t i = 1; i < message.arguments.size(); ++i)
    {
      if (!message.arguments[i].empty())
        {
          if (i != 1)
            mode_arguments += " ";
          mode_arguments += message.arguments[i];
        }
    }
louiz’'s avatar
louiz’ committed
804
  this->bridge->send_message(iid, "", "Mode "s + iid.get_local() +
805
                                      " [" + mode_arguments + "] by " + user.nick,
louiz’'s avatar
louiz’ committed
806
                             true);
louiz’'s avatar
louiz’ committed
807
  const IrcChannel* channel = this->get_channel(iid.get_local());
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
  if (!channel)
    return;

  // parse the received modes, we need to handle things like "+m-oo coucou toutou"
  const std::string modes = message.arguments[1];
  // a list of modified IrcUsers. When we applied all modes, we check the
  // modes that now applies to each of them, and send a notification for
  // each one. This is to disallow sending two notifications or more when a
  // single MODE command changes two or more modes on the same participant
  std::set<const IrcUser*> modified_users;
  // If it is true, the modes are added, if it’s false they are
  // removed. When we encounter the '+' char, the value is changed to true,
  // and with '-' it is changed to false.
  bool add = true;
  bool use_arg;
  size_t arg_pos = 2;
  for (const char c: modes)
    {
      if (c == '+')
        add = true;
      else if (c == '-')
        add = false;
      else
        { // lookup the mode symbol in the 4 chanmodes lists, depending on
          // the list where it is found, it takes an argument or not
          size_t type;
          for (type = 0; type < 4; ++type)
            if (this->chanmodes[type].find(c) != std::string::npos)
              break;
          if (type == 4)        // if mode was not found
            {
              // That mode can also be of type B if it is present in the
              // prefix_to_mode map
              for (const std::pair<char, char>& pair: this->prefix_to_mode)
                if (pair.second == c)
                  {
                    type = 1;
                    break;
                  }
            }
          // modes of type A, B or C (but only with add == true)
          if (type == 0 || type == 1 ||
              (type == 2 && add == true))
            use_arg = true;
          else // modes of type C (but only with add == false), D, or unknown
            use_arg = false;
          if (use_arg == true && message.arguments.size() > arg_pos)
            {
              const std::string target = message.arguments[arg_pos++];
              IrcUser* user = channel->find_user(target);
              if (!user)
                {
                  log_warning("Trying to set mode for non-existing user '" << target
louiz’'s avatar
louiz’ committed
861
                              << "' in channel" << iid.get_local());
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
                  return;
                }
              if (add)
                user->add_mode(c);
              else
                user->remove_mode(c);
              modified_users.insert(user);
            }
        }
    }
  for (const IrcUser* u: modified_users)
    {
      char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes);
      this->bridge->send_affiliation_role_change(iid, u->nick, most_significant_mode);
    }
louiz’'s avatar
louiz’ committed
877
878
879
880
881
}

void IrcClient::on_user_mode(const IrcMessage& message)
{
  this->bridge->send_xmpp_message(this->hostname, "",
louiz’'s avatar
louiz’ committed
882
                                  "User mode for "s + message.arguments[0] +
louiz’'s avatar
louiz’ committed
883
884
                                  " is [" + message.arguments[1] + "]");
}
885

886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
void IrcClient::on_unknown_message(const IrcMessage& message)
{
  if (message.arguments.size() < 2)
    return ;
  std::string from = message.prefix;
  const std::string to = message.arguments[0];
  std::stringstream ss;
  for (auto it = message.arguments.begin() + 1; it != message.arguments.end(); ++it)
    {
      ss << *it;
      if (it + 1 != message.arguments.end())
        ss << " ";
    }
  this->bridge->send_xmpp_message(this->hostname, from, ss.str());
}

902
903
size_t IrcClient::number_of_joined_channels() const
{
904
905
906
907
  if (this->dummy_channel.joined)
    return this->channels.size() + 1;
  else
    return this->channels.size();
908
}
louiz’'s avatar
louiz’ committed
909
910
911
912
913

DummyIrcChannel& IrcClient::get_dummy_channel()
{
  return this->dummy_channel;
}
louiz’'s avatar
louiz’ committed
914
915
916
917
918
919
920
921

void IrcClient::leave_dummy_channel(const std::string& exit_message)
{
  if (!this->dummy_channel.joined)
    return;
  this->dummy_channel.joined = false;
  this->dummy_channel.joining = false;
  this->dummy_channel.remove_all_users();
louiz’'s avatar
louiz’ committed
922
  this->bridge->send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true);
louiz’'s avatar
louiz’ committed
923
}