irc_client.cpp 37.6 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
30
31
32
33
34
35
36
/**
 * Define a map of functions to be called for each IRC command we can
 * handle.
 */
typedef void (IrcClient::*irc_callback_t)(const IrcMessage&);

static const std::unordered_map<std::string,
                                std::pair<irc_callback_t, std::pair<std::size_t, std::size_t>>> irc_callbacks = {
  {"NOTICE", {&IrcClient::on_notice, {2, 0}}},
  {"002", {&IrcClient::forward_server_message, {2, 0}}},
  {"003", {&IrcClient::forward_server_message, {2, 0}}},
louiz’'s avatar
louiz’ committed
37
  {"004", {&IrcClient::on_server_myinfo, {4, 0}}},
38
39
40
41
42
43
44
  {"005", {&IrcClient::on_isupport_message, {0, 0}}},
  {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}},
  {"321", {&IrcClient::on_rpl_liststart, {0, 0}}},
  {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}},
  {"322", {&IrcClient::on_rpl_list, {0, 0}}},
  {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}},
  {"323", {&IrcClient::on_rpl_listend, {0, 0}}},
louiz’'s avatar
louiz’ committed
45
46
  {"RPL_NOTOPIC", {&IrcClient::on_empty_topic, {0, 0}}},
  {"331", {&IrcClient::on_empty_topic, {0, 0}}},
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}},
  {"375", {&IrcClient::empty_motd, {0, 0}}},
  {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}},
  {"372", {&IrcClient::on_motd_line, {2, 0}}},
  {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}},
  {"376", {&IrcClient::send_motd, {0, 0}}},
  {"JOIN", {&IrcClient::on_channel_join, {1, 0}}},
  {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}},
  {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}},
  {"332", {&IrcClient::on_topic_received, {2, 0}}},
  {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}},
  {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
  {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
  {"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
  {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
  {"001", {&IrcClient::on_welcome_message, {1, 0}}},
  {"PART", {&IrcClient::on_part, {1, 0}}},
  {"ERROR", {&IrcClient::on_error, {1, 0}}},
  {"QUIT", {&IrcClient::on_quit, {0, 0}}},
  {"NICK", {&IrcClient::on_nick, {1, 0}}},
  {"MODE", {&IrcClient::on_mode, {1, 0}}},
  {"PING", {&IrcClient::send_pong_command, {1, 0}}},
  {"PONG", {&IrcClient::on_pong, {0, 0}}},
  {"KICK", {&IrcClient::on_kick, {3, 0}}},

  {"401", {&IrcClient::on_generic_error, {2, 0}}},
  {"402", {&IrcClient::on_generic_error, {2, 0}}},
  {"403", {&IrcClient::on_generic_error, {2, 0}}},
  {"404", {&IrcClient::on_generic_error, {2, 0}}},
  {"405", {&IrcClient::on_generic_error, {2, 0}}},
  {"406", {&IrcClient::on_generic_error, {2, 0}}},
  {"407", {&IrcClient::on_generic_error, {2, 0}}},
  {"408", {&IrcClient::on_generic_error, {2, 0}}},
  {"409", {&IrcClient::on_generic_error, {2, 0}}},
  {"410", {&IrcClient::on_generic_error, {2, 0}}},
  {"411", {&IrcClient::on_generic_error, {2, 0}}},
  {"412", {&IrcClient::on_generic_error, {2, 0}}},
  {"414", {&IrcClient::on_generic_error, {2, 0}}},
  {"421", {&IrcClient::on_generic_error, {2, 0}}},
  {"422", {&IrcClient::on_generic_error, {2, 0}}},
  {"423", {&IrcClient::on_generic_error, {2, 0}}},
  {"424", {&IrcClient::on_generic_error, {2, 0}}},
  {"431", {&IrcClient::on_generic_error, {2, 0}}},
  {"436", {&IrcClient::on_generic_error, {2, 0}}},
  {"441", {&IrcClient::on_generic_error, {2, 0}}},
  {"442", {&IrcClient::on_generic_error, {2, 0}}},
  {"443", {&IrcClient::on_generic_error, {2, 0}}},
  {"444", {&IrcClient::on_generic_error, {2, 0}}},
  {"446", {&IrcClient::on_generic_error, {2, 0}}},
  {"451", {&IrcClient::on_generic_error, {2, 0}}},
  {"461", {&IrcClient::on_generic_error, {2, 0}}},
  {"462", {&IrcClient::on_generic_error, {2, 0}}},
  {"463", {&IrcClient::on_generic_error, {2, 0}}},
  {"464", {&IrcClient::on_generic_error, {2, 0}}},
  {"465", {&IrcClient::on_generic_error, {2, 0}}},
  {"467", {&IrcClient::on_generic_error, {2, 0}}},
  {"470", {&IrcClient::on_generic_error, {2, 0}}},
  {"471", {&IrcClient::on_generic_error, {2, 0}}},
  {"472", {&IrcClient::on_generic_error, {2, 0}}},
  {"473", {&IrcClient::on_generic_error, {2, 0}}},
  {"474", {&IrcClient::on_generic_error, {2, 0}}},
  {"475", {&IrcClient::on_generic_error, {2, 0}}},
  {"476", {&IrcClient::on_generic_error, {2, 0}}},
  {"477", {&IrcClient::on_generic_error, {2, 0}}},
  {"481", {&IrcClient::on_generic_error, {2, 0}}},
  {"482", {&IrcClient::on_generic_error, {2, 0}}},
  {"483", {&IrcClient::on_generic_error, {2, 0}}},
  {"484", {&IrcClient::on_generic_error, {2, 0}}},
  {"485", {&IrcClient::on_generic_error, {2, 0}}},
  {"487", {&IrcClient::on_generic_error, {2, 0}}},
  {"491", {&IrcClient::on_generic_error, {2, 0}}},
  {"501", {&IrcClient::on_generic_error, {2, 0}}},
  {"502", {&IrcClient::on_generic_error, {2, 0}}},
};
121

122
123
IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
                     const std::string& nickname, const std::string& username,
louiz’'s avatar
louiz’ committed
124
                     const std::string& realname, const std::string& user_hostname,
125
                     Bridge& bridge):
126
  TCPSocketHandler(poller),
127
  hostname(hostname),
louiz’'s avatar
louiz’ committed
128
  user_hostname(user_hostname),
129
  username(username),
130
131
  realname(realname),
  current_nick(nickname),
louiz’'s avatar
louiz’ committed
132
  bridge(bridge),
louiz’'s avatar
louiz’ committed
133
  welcomed(false),
134
135
  chanmodes({"", "", "", ""}),
  chantypes({'#', '&'})
136
{
137
138
139
140
141
142
143
144
  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.";
145
#ifdef USE_DATABASE
146
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
147
148
149
150
151
152
153
154
155
156
157
                                                  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
158
  this->ports_to_try.emplace("6667", false); // standard non-encrypted port
159
# ifdef BOTAN_FOUND
louiz’'s avatar
louiz’ committed
160
161
  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
162
163
# endif // BOTAN_FOUND
#endif // USE_DATABASE
164
165
166
167
}

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

173
174
void IrcClient::start()
{
175
176
  if (this->is_connecting() || this->is_connected())
    return;
louiz’'s avatar
louiz’ committed
177
178
179
180
  std::string port;
  bool tls;
  std::tie(port, tls) = this->ports_to_try.top();
  this->ports_to_try.pop();
181
  this->bridge.send_xmpp_message(this->hostname, "", "Connecting to "s +
louiz’'s avatar
louiz’ committed
182
183
                                  this->hostname + ":" + port + " (" +
                                  (tls ? "encrypted" : "not encrypted") + ")");
louiz’'s avatar
louiz’ committed
184
185
186

  this->bind_addr = Config::get("outgoing_bind", "");

187
188
189
190
191
192
193
#ifdef BOTAN_FOUND
# ifdef USE_DATABASE
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
                                                  this->get_hostname());
  this->credential_manager.set_trusted_fingerprint(options.trustedFingerprint);
# endif
#endif
louiz’'s avatar
louiz’ committed
194
  this->connect(this->hostname, port, tls);
195
196
197
198
}

void IrcClient::on_connection_failed(const std::string& reason)
{
199
  this->bridge.send_xmpp_message(this->hostname, "",
louiz’'s avatar
louiz’ committed
200
                                  "Connection failed: "s + reason);
201
202
203
204
205

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

louiz’'s avatar
louiz’ committed
206
  if (this->ports_to_try.empty())
207
    {
louiz’'s avatar
louiz’ committed
208
      // Send an error message for all room that the user wanted to join
209
      for (const auto& tuple: this->channels_to_join)
louiz’'s avatar
louiz’ committed
210
        {
211
          Iid iid(std::get<0>(tuple) + "%" + this->hostname);
212
          this->bridge.send_presence_error(iid, this->current_nick,
213
214
                                            "cancel", "item-not-found",
                                            "", reason);
louiz’'s avatar
louiz’ committed
215
        }
216
    }
louiz’'s avatar
louiz’ committed
217
218
  else                          // try the next port
    this->start();
219
220
}

louiz’'s avatar
louiz’ committed
221
222
void IrcClient::on_connected()
{
louiz’'s avatar
louiz’ committed
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
  const auto webirc_password = Config::get("webirc_password", "");
  static std::string resolved_ip;

  if (!webirc_password.empty())
    {
      if (!resolved_ip.empty())
        this->send_webirc_command(webirc_password, resolved_ip);
      else
        {  // Start resolving the hostname of the user, and call
           // on_connected again when it’s done
          this->dns_resolver.resolve(this->user_hostname, "5222",
                                     [this](const struct addrinfo* addr)
                                     {
                                       resolved_ip = addr_to_string(addr);
                                       // Only continue the process if we
                                       // didn’t get connected while we were
                                       // resolving
                                       if (this->is_connected())
                                         this->on_connected();
                                     },
                                     [this](const char* error_msg)
                                     {
                                       if (this->is_connected())
                                         {
                                           this->on_connection_close("Could not resolve hostname "s + this->user_hostname +
                                                                     ": " + error_msg);
                                           this->send_quit_command("");
                                         }
                                     });
          return;
        }
    }

louiz’'s avatar
louiz’ committed
256
257
258
  this->send_message({"CAP", {"REQ", "multi-prefix"}});
  this->send_message({"CAP", {"END"}});

259
#ifdef USE_DATABASE
260
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
261
262
263
264
                                                  this->get_hostname());
  if (!options.pass.value().empty())
    this->send_pass_command(options.pass.value());
#endif
265

266
  this->send_nick_command(this->current_nick);
267

268
#ifdef USE_DATABASE
269
270
271
272
273
274
275
276
  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);
    }
277
278
279
  else
    this->send_user_command(this->username, this->realname);
#else
280
  this->send_user_command(this->username, this->realname);
281
#endif
louiz’'s avatar
louiz’ committed
282
  this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
283
  this->send_pending_data();
louiz’'s avatar
louiz’ committed
284
285
}

286
void IrcClient::on_connection_close(const std::string& error_msg)
287
{
288
  std::string message = "Connection closed";
289
290
  if (!error_msg.empty())
    message += ": " + error_msg;
291
292
  else
    message += ".";
293
294
  const IrcMessage error{"ERROR", {message}};
  this->on_error(error);
295
  log_warning(message);
296
297
}

louiz’'s avatar
louiz’ committed
298
IrcChannel* IrcClient::get_channel(const std::string& n)
299
{
louiz’'s avatar
louiz’ committed
300
  if (n.empty())
301
    return &this->dummy_channel;
louiz’'s avatar
louiz’ committed
302
  const std::string name = utils::tolower(n);
303
304
305
306
307
308
309
310
311
312
313
  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
314
315
bool IrcClient::is_channel_joined(const std::string& name)
{
louiz’'s avatar
louiz’ committed
316
317
  IrcChannel* channel = this->get_channel(name);
  return channel->joined;
louiz’'s avatar
louiz’ committed
318
319
}

louiz’'s avatar
louiz’ committed
320
321
322
323
324
std::string IrcClient::get_own_nick() const
{
  return this->current_nick;
}

325
void IrcClient::parse_in_buffer(const size_t)
326
{
louiz’'s avatar
louiz’ committed
327
328
329
330
331
332
333
  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);
334
      log_debug("IRC RECEIVING: (" << this->get_hostname() << ") " << message);
335
336
337

      // Call the standard callback (if any), associated with the command
      // name that we just received.
338
339
      auto it = irc_callbacks.find(message.command);
      if (it != irc_callbacks.end())
340
        {
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
          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());
              }
            }
358
        }
359
      else
360
361
362
363
364
        {
          log_info("No handler for command " << message.command <<
                   ", forwarding the arguments to the user");
          this->on_unknown_message(message);
        }
365
      // Try to find a waiting_iq, which response will be triggered by this IrcMessage
366
      this->bridge.trigger_on_irc_message(this->hostname, message);
louiz’'s avatar
louiz’ committed
367
368
369
370
371
    }
}

void IrcClient::send_message(IrcMessage&& message)
{
372
  log_debug("IRC SENDING: (" << this->get_hostname() << ") " << message);
louiz’'s avatar
louiz’ committed
373
374
375
376
377
378
  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
379
      if (arg.find(" ") != std::string::npos ||
louiz’'s avatar
louiz’ committed
380
          (!arg.empty() && arg[0] == ':'))
louiz’'s avatar
louiz’ committed
381
382
383
384
385
386
387
        {
          res += " :" + arg;
          break;
        }
      res += " " + arg;
    }
  res += "\r\n";
388
  this->send_data(std::move(res));
louiz’'s avatar
louiz’ committed
389
390
}

louiz’'s avatar
louiz’ committed
391
392
void IrcClient::send_raw(const std::string& txt)
{
393
  log_debug("IRC SENDING (raw): (" << this->get_hostname() << ") " << txt);
louiz’'s avatar
louiz’ committed
394
395
396
  this->send_data(txt + "\r\n");
}

louiz’'s avatar
louiz’ committed
397
398
void IrcClient::send_user_command(const std::string& username, const std::string& realname)
{
louiz’'s avatar
louiz’ committed
399
  this->send_message(IrcMessage("USER", {username, this->user_hostname, "ignored", realname}));
louiz’'s avatar
louiz’ committed
400
401
402
403
404
405
406
}

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

407
408
409
410
411
void IrcClient::send_pass_command(const std::string& password)
{
  this->send_message(IrcMessage("PASS", {password}));
}

louiz’'s avatar
louiz’ committed
412
413
414
415
416
void IrcClient::send_webirc_command(const std::string& password, const std::string& user_ip)
{
  this->send_message(IrcMessage("WEBIRC", {password, "biboumi", this->user_hostname, user_ip}));
}

417
418
419
420
421
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}));
}

422
423
424
425
426
void IrcClient::send_list_command()
{
  this->send_message(IrcMessage("LIST", {}));
}

427
428
429
430
431
void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
{
  this->send_message(IrcMessage("TOPIC", {chan_name, topic}));
}

432
void IrcClient::send_quit_command(const std::string& reason)
louiz’'s avatar
louiz’ committed
433
{
434
  this->send_message(IrcMessage("QUIT", {reason}));
louiz’'s avatar
louiz’ committed
435
436
}

437
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
louiz’'s avatar
louiz’ committed
438
{
louiz’'s avatar
louiz’ committed
439
  if (this->welcomed == false)
440
    this->channels_to_join.emplace_back(chan_name, password);
441
442
  else if (password.empty())
    this->send_message(IrcMessage("JOIN", {chan_name}));
louiz’'s avatar
louiz’ committed
443
  else
444
    this->send_message(IrcMessage("JOIN", {chan_name, password}));
445
  this->start();
446
447
}

louiz’'s avatar
louiz’ committed
448
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
449
{
louiz’'s avatar
louiz’ committed
450
451
452
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == false)
    {
louiz’'s avatar
louiz’ committed
453
      log_warning("Cannot send message to channel " << chan_name << ", it is not joined");
louiz’'s avatar
louiz’ committed
454
455
      return false;
    }
456
457
458
459
460
461
462
463
464
  // 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
465
466
467
  return true;
}

louiz’'s avatar
louiz’ committed
468
void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
469
{
470
471
472
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
louiz’'s avatar
louiz’ committed
473
      this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)}));
474
475
      pos += 400;
    }
476
477
478
  // 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
479
480
}

louiz’'s avatar
louiz’ committed
481
482
483
484
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
485
486
    {
      if (chan_name.empty())
louiz’'s avatar
louiz’ committed
487
        this->leave_dummy_channel(status_message);
louiz’'s avatar
louiz’ committed
488
489
490
      else
        this->send_message(IrcMessage("PART", {chan_name, status_message}));
    }
louiz’'s avatar
louiz’ committed
491
492
493
494
495
496
497
498
}

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
499
500
}

louiz’'s avatar
louiz’ committed
501
502
503
504
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
505
506
}

louiz’'s avatar
louiz’ committed
507
void IrcClient::on_pong(const IrcMessage&)
508
509
510
{
}

511
512
513
514
515
void IrcClient::send_ping_command()
{
  this->send_message(IrcMessage("PING", {"biboumi"}));
}

516
517
518
519
520
void IrcClient::forward_server_message(const IrcMessage& message)
{
  const std::string from = message.prefix;
  const std::string body = message.arguments[1];

521
  this->bridge.send_xmpp_message(this->hostname, from, body);
522
}
louiz’'s avatar
louiz’ committed
523

524
525
526
527
528
529
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];

530
531
532
533
  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 ;

534
  if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end())
535
    {
louiz’'s avatar
louiz’ committed
536
      // The notice is for us precisely.
537
538
539
540
541
542
543
544
545

      // 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)
546
          this->bridge.send_message({nick + "!" + this->hostname}, nick, body,
547
548
549
                                     false);
        }
      else
550
        this->bridge.send_xmpp_message(this->hostname, from, body);
551
    }
552
553
  else
    {
554
555
556
      // 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
557
      IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 "s + body});
558
559
560
561
      this->on_channel_message(modified_message);
    }
}

louiz’'s avatar
louiz’ committed
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
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] != ')')
583
584
585
586
          {
            this->sorted_user_modes.push_back(token[i]);
            this->prefix_to_mode[token[j++]] = token[i++];
          }
louiz’'s avatar
louiz’ committed
587
      }
588
589
590
591
592
    else if (token.substr(0, 10) == "CHANTYPES=")
      {
        // Remove the default types, they apply only if no other value is
        // specified.
        this->chantypes.clear();
593
        size_t i = 10;
594
595
596
        while (i < token.size())
          this->chantypes.insert(token[i++]);
      }
louiz’'s avatar
louiz’ committed
597
598
  }
}
599

louiz’'s avatar
louiz’ committed
600
601
602
603
void IrcClient::on_server_myinfo(const IrcMessage&)
{
}

604
605
void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
{
606
  this->bridge.send_xmpp_message(this->hostname, from, message);
607
608
}

609
610
void IrcClient::set_and_forward_user_list(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
611
  const std::string chan_name = utils::tolower(message.arguments[2]);
612
613
614
615
  IrcChannel* channel = this->get_channel(chan_name);
  std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
  for (const std::string& nick: nicks)
    {
616
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
617
618
      if (user->nick != channel->get_self()->nick)
        {
619
          this->bridge.send_user_join(this->hostname, chan_name, user,
620
621
                                       user->get_most_significant_mode(this->sorted_user_modes),
                                       false);
622
623
624
625
626
        }
      else
        {
          // we now know the modes of self, so copy the modes into self
          channel->get_self()->modes = user->modes;
627
628
629
630
        }
    }
}

louiz’'s avatar
louiz’ committed
631
void IrcClient::on_channel_join(const IrcMessage& message)
632
{
louiz’'s avatar
louiz’ committed
633
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
634
635
636
637
638
  IrcChannel* channel;
  if (chan_name.empty())
    channel = &this->dummy_channel;
  else
    channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
639
640
  const std::string nick = message.prefix;
  if (channel->joined == false)
641
    channel->set_self(nick);
louiz’'s avatar
louiz’ committed
642
643
  else
    {
644
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
645
      this->bridge.send_user_join(this->hostname, chan_name, user,
646
647
                                   user->get_most_significant_mode(this->sorted_user_modes),
                                   false);
louiz’'s avatar
louiz’ committed
648
    }
649
650
}

louiz’'s avatar
louiz’ committed
651
652
653
654
655
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
656
657
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
louiz’'s avatar
louiz’ committed
658
  const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
659
  bool muc = true;
louiz’'s avatar
louiz’ committed
660
  if (!this->get_channel(iid.get_local())->joined)
louiz’'s avatar
louiz’ committed
661
    {
louiz’'s avatar
louiz’ committed
662
663
      iid.is_user = true;
      iid.set_local(nick);
louiz’'s avatar
louiz’ committed
664
665
      muc = false;
    }
louiz’'s avatar
louiz’ committed
666
667
  else
    iid.is_channel = true;
668
669
670
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")
671
        this->bridge.send_message(iid, nick,
louiz’'s avatar
louiz’ committed
672
                  "/me"s + body.substr(7, body.size() - 8), muc);
673
      else if (body.substr(1, 8) == "VERSION\01")
674
        this->bridge.send_iq_version_request(nick, this->hostname);
675
      else if (body.substr(1, 5) == "PING ")
676
        this->bridge.send_xmpp_ping_request(utils::tolower(nick), this->hostname,
677
                                             body.substr(6, body.size() - 7));
678
679
    }
  else
680
    this->bridge.send_message(iid, nick, body, muc);
louiz’'s avatar
louiz’ committed
681
682
}

683
684
685
686
687
688
689
690
691
692
693
694
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
695
void IrcClient::empty_motd(const IrcMessage&)
696
697
698
699
{
  this->motd.erase();
}

louiz’'s avatar
louiz’ committed
700
701
702
703
704
705
706
707
708
void IrcClient::on_empty_topic(const IrcMessage& message)
{
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 1]);
  log_debug("empty topic for " << chan_name);
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel)
    channel->topic.clear();
}

709
710
711
712
713
714
715
716
717
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
718
void IrcClient::send_motd(const IrcMessage&)
719
{
720
  this->bridge.send_xmpp_message(this->hostname, "", this->motd);
721
722
}

723
724
void IrcClient::on_topic_received(const IrcMessage& message)
{
725
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]);
726
  IrcChannel* channel = this->get_channel(chan_name);
727
  channel->topic = message.arguments[message.arguments.size() - 1];
louiz’'s avatar
louiz’ committed
728
  if (channel->joined)
729
    this->bridge.send_topic(this->hostname, chan_name, channel->topic);
730
731
732
733
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
734
  const std::string chan_name = utils::tolower(message.arguments[1]);
735
  IrcChannel* channel = this->get_channel(chan_name);
736
  channel->joined = true;
737
  this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
738
739
                               channel->get_self()->get_most_significant_mode(this->sorted_user_modes),
                               true);
740
  this->bridge.send_topic(this->hostname, chan_name, channel->topic);
741
}
louiz’'s avatar
louiz’ committed
742

743
744
745
746
747
748
749
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);
}

750
751
752
753
754
755
756
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
757
758
759
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
760
    this->bridge.send_nickname_conflict_error(iid, nickname);
761
762
763
  }
}

764
765
766
767
768
769
770
771
772
773
774
775
776
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;
777
    this->bridge.send_presence_error(iid, nickname,
778
779
780
781
782
                                      "cancel", "not-acceptable",
                                      "", txt);
  }
}

783
784
785
786
787
788
789
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
790
791
792
793
void IrcClient::on_welcome_message(const IrcMessage& message)
{
  this->current_nick = message.arguments[0];
  this->welcomed = true;
794
#ifdef USE_DATABASE
795
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
796
797
798
799
                                                  this->get_hostname());
  if (!options.afterConnectionCommand.value().empty())
    this->send_raw(options.afterConnectionCommand.value());
#endif
800
801
  // Install a repeated events to regularly send a PING
  TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
802
                                                      "PING"s + this->hostname + this->bridge.get_jid()));
803
804
  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
805
  this->channels_to_join.clear();
louiz’'s avatar
louiz’ committed
806
807
808
809
810
811
812
813
814
815
816
817
  // 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
818
}
louiz’'s avatar
louiz’ committed
819
820
821

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
822
  const std::string chan_name = message.arguments[0];
louiz’'s avatar
louiz’ committed
823
  IrcChannel* channel = this->get_channel(chan_name);
824
825
  if (!channel->joined)
    return ;
louiz’'s avatar
louiz’ committed
826
827
828
829
830
831
832
833
834
  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
835
836
837
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.is_channel = true;
louiz’'s avatar
louiz’ committed
838
839
      bool self = channel->get_self()->nick == nick;
      if (self)
louiz’'s avatar
louiz’ committed
840
      {
louiz’'s avatar
louiz’ committed
841
        channel->joined = false;
louiz’'s avatar
louiz’ committed
842
        this->channels.erase(utils::tolower(chan_name));
louiz’'s avatar
louiz’ committed
843
844
845
        // channel pointer is now invalid
        channel = nullptr;
      }
846
      this->bridge.send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self);
louiz’'s avatar
louiz’ committed
847
848
    }
}
louiz’'s avatar
louiz’ committed
849

louiz’'s avatar
louiz’ committed
850
851
852
853
854
855
856
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
857
858
859
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
louiz’'s avatar
louiz’ committed
860
    IrcChannel* channel = it->second.get();
861
862
    if (!channel->joined)
      continue;
louiz’'s avatar
louiz’ committed
863
    std::string own_nick = channel->get_self()->nick;
864
    this->bridge.send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
louiz’'s avatar
louiz’ committed
865
  }
louiz’'s avatar
louiz’ committed
866
  this->channels.clear();
louiz’'s avatar
louiz’ committed
867
  this->send_gateway_message("ERROR: "s + leave_message);
louiz’'s avatar
louiz’ committed
868
869
}

louiz’'s avatar
louiz’ committed
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
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
885
886
887
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
888
          this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
889
890
891
        }
    }
}
louiz’'s avatar
louiz’ committed
892
893
894
895
896
897
898
899
900
901
902
903
904

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
905
906
907
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
908
909
          const bool self = channel->get_self()->nick == old_nick;
          const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
910
          this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);
louiz’'s avatar
louiz’ committed
911
912
913
914
915
916
917
918
919
920
          user->nick = new_nick;
          if (self)
            {
              channel->get_self()->nick = new_nick;
              this->current_nick = new_nick;
            }
        }
    }
}

921
922
void IrcClient::on_kick(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
923
  const std::string chan_name = utils::tolower(message.arguments[0]);
924
925
926
  const std::string target = message.arguments[1];
  const std::string reason = message.arguments[2];
  IrcChannel* channel = this->get_channel(chan_name);
927
928
  if (!channel->joined)
    return ;
929
930
931
932
  if (channel->get_self()->nick == target)
    channel->joined = false;
  IrcUser author(message.prefix);
  Iid iid;
louiz’'s avatar
louiz’ committed
933
934
935
  iid.set_local(chan_name);
  iid.set_server(this->hostname);
  iid.is_channel = true;
936
  this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick);
937
938
}

louiz’'s avatar
louiz’ committed
939
940
941
void IrcClient::on_mode(const IrcMessage& message)
{
  const std::string target = message.arguments[0];
942
  if (this->chantypes.find(target[0]) != this->chantypes.end())
louiz’'s avatar
louiz’ committed
943
944
945
946
947
948
949
950
951
952
    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
953
954
955
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
  iid.is_channel = true;
louiz’'s avatar
louiz’ committed
956
  IrcUser user(message.prefix);
957
958
959
960
961
962
963
964
965
966
  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];
        }
    }
967
  this->bridge.send_message(iid, "", "Mode "s + iid.get_local() +
968
                                      " [" + mode_arguments + "] by " + user.nick,
louiz’'s avatar
louiz’ committed
969
                             true);
louiz’'s avatar
louiz’ committed
970
  const IrcChannel* channel = this->get_channel(iid.get_local());
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
  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
For faster browsing, not all history is shown. View entire blame