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

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

15
#include <sstream>
16 17
#include <iostream>
#include <stdexcept>
18
#include <algorithm>
louiz’'s avatar
louiz’ committed
19
#include <cstring>
20

21
#include <chrono>
louiz’'s avatar
louiz’ committed
22
#include <string>
23

24
#include "biboumi.h"
louiz’'s avatar
louiz’ committed
25

louiz’'s avatar
louiz’ committed
26
using namespace std::string_literals;
27
using namespace std::chrono_literals;
louiz’'s avatar
louiz’ committed
28

29 30 31 32
/**
 * Define a map of functions to be called for each IRC command we can
 * handle.
 */
louiz’'s avatar
louiz’ committed
33
using IrcCallback = void (IrcClient::*)(const IrcMessage&);
34 35

static const std::unordered_map<std::string,
louiz’'s avatar
louiz’ committed
36
                                std::pair<IrcCallback, std::pair<std::size_t, std::size_t>>> irc_callbacks = {
37 38 39
  {"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
40
  {"004", {&IrcClient::on_server_myinfo, {4, 0}}},
41 42 43 44 45 46 47
  {"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
48 49
  {"RPL_NOTOPIC", {&IrcClient::on_empty_topic, {0, 0}}},
  {"331", {&IrcClient::on_empty_topic, {0, 0}}},
50
  {"341", {&IrcClient::on_invited, {3, 0}}},
51 52 53 54 55 56 57 58 59 60 61
  {"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}}},
62 63
  {"333", {&IrcClient::on_topic_who_time_received, {4, 0}}},
  {"RPL_TOPICWHOTIME", {&IrcClient::on_topic_who_time_received, {4, 0}}},
64
  {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
65
  {"396", {&IrcClient::on_own_host_received, {2, 0}}},
66 67 68
  {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
  {"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
  {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
69
  {"443", {&IrcClient::on_useronchannel, {3, 0}}},
70
  {"475", {&IrcClient::on_channel_bad_key, {3, 0}}},
71
  {"ERR_USERONCHANNEL", {&IrcClient::on_useronchannel, {3, 0}}},
72 73 74 75 76 77 78 79 80
  {"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}}},
louiz’'s avatar
louiz’ committed
81
  {"INVITE", {&IrcClient::on_invite, {2, 0}}},
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 121 122 123 124 125 126 127 128 129

  {"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}}},
  {"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}}},
  {"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}}},
};
130

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

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

182 183
void IrcClient::start()
{
184 185
  if (this->is_connecting() || this->is_connected())
    return;
louiz’'s avatar
louiz’ committed
186 187 188 189
  std::string port;
  bool tls;
  std::tie(port, tls) = this->ports_to_try.top();
  this->ports_to_try.pop();
190
  this->bridge.send_xmpp_message(this->hostname, "", "Connecting to "s +
louiz’'s avatar
louiz’ committed
191 192
                                  this->hostname + ":" + port + " (" +
                                  (tls ? "encrypted" : "not encrypted") + ")");
louiz’'s avatar
louiz’ committed
193 194 195

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

196 197 198 199 200 201 202
#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
203
  this->connect(this->hostname, port, tls);
204 205 206 207
}

void IrcClient::on_connection_failed(const std::string& reason)
{
208
  this->bridge.send_xmpp_message(this->hostname, "",
louiz’'s avatar
louiz’ committed
209
                                  "Connection failed: "s + reason);
210 211 212 213 214

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

louiz’'s avatar
louiz’ committed
215
  if (this->ports_to_try.empty())
216
    {
louiz’'s avatar
louiz’ committed
217
      // Send an error message for all room that the user wanted to join
218
      for (const auto& tuple: this->channels_to_join)
louiz’'s avatar
louiz’ committed
219
        {
220
          Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
221
          this->bridge.send_presence_error(iid, this->current_nick,
222 223
                                            "cancel", "item-not-found",
                                            "", reason);
louiz’'s avatar
louiz’ committed
224
        }
225
    }
louiz’'s avatar
louiz’ committed
226 227
  else                          // try the next port
    this->start();
228 229
}

louiz’'s avatar
louiz’ committed
230 231
void IrcClient::on_connected()
{
louiz’'s avatar
louiz’ committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
  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
265 266 267
  this->send_message({"CAP", {"REQ", "multi-prefix"}});
  this->send_message({"CAP", {"END"}});

268
#ifdef USE_DATABASE
269
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
270 271 272 273
                                                  this->get_hostname());
  if (!options.pass.value().empty())
    this->send_pass_command(options.pass.value());
#endif
274

275
  this->send_nick_command(this->current_nick);
276

277
#ifdef USE_DATABASE
278 279 280 281 282 283 284 285
  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);
    }
286 287 288
  else
    this->send_user_command(this->username, this->realname);
#else
289
  this->send_user_command(this->username, this->realname);
290
#endif
louiz’'s avatar
louiz’ committed
291
  this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
292
  this->send_pending_data();
louiz’'s avatar
louiz’ committed
293 294
}

295
void IrcClient::on_connection_close(const std::string& error_msg)
296
{
297
  std::string message = "Connection closed";
298 299
  if (!error_msg.empty())
    message += ": " + error_msg;
300 301
  else
    message += ".";
302 303
  const IrcMessage error{"ERROR", {message}};
  this->on_error(error);
304
  log_warning(message);
305 306
}

louiz’'s avatar
louiz’ committed
307
IrcChannel* IrcClient::get_channel(const std::string& n)
308
{
louiz’'s avatar
louiz’ committed
309
  if (n.empty())
310
    return &this->dummy_channel;
louiz’'s avatar
louiz’ committed
311
  const std::string name = utils::tolower(n);
312 313 314 315 316 317 318 319 320 321 322
  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
323 324
bool IrcClient::is_channel_joined(const std::string& name)
{
louiz’'s avatar
louiz’ committed
325 326
  IrcChannel* channel = this->get_channel(name);
  return channel->joined;
louiz’'s avatar
louiz’ committed
327 328
}

louiz’'s avatar
louiz’ committed
329 330 331 332 333
std::string IrcClient::get_own_nick() const
{
  return this->current_nick;
}

334
void IrcClient::parse_in_buffer(const size_t)
335
{
louiz’'s avatar
louiz’ committed
336 337 338 339 340 341
  while (true)
    {
      auto pos = this->in_buf.find("\r\n");
      if (pos == std::string::npos)
        break ;
      IrcMessage message(this->in_buf.substr(0, pos));
342
      this->consume_in_buffer(pos + 2);
343
      log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
344 345 346

      // Call the standard callback (if any), associated with the command
      // name that we just received.
347 348
      auto it = irc_callbacks.find(message.command);
      if (it != irc_callbacks.end())
349
        {
350 351 352 353 354 355
          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))
356 357
            log_warning("Invalid number of arguments for IRC command “", message.command,
                        "”: ", message.arguments.size());
358 359 360 361 362 363
          else
            {
              const auto& cb = it->second.first;
              try {
                (this->*(cb))(message);
              } catch (const std::exception& e) {
364
                log_error("Unhandled exception: ", e.what());
365 366
              }
            }
367
        }
368
      else
369
        {
370
          log_info("No handler for command ", message.command,
371 372 373
                   ", forwarding the arguments to the user");
          this->on_unknown_message(message);
        }
374
      // Try to find a waiting_iq, which response will be triggered by this IrcMessage
375
      this->bridge.trigger_on_irc_message(this->hostname, message);
louiz’'s avatar
louiz’ committed
376 377 378 379 380
    }
}

void IrcClient::send_message(IrcMessage&& message)
{
381
  log_debug("IRC SENDING: (", this->get_hostname(), ") ", message);
louiz’'s avatar
louiz’ committed
382 383 384
  std::string res;
  if (!message.prefix.empty())
    res += ":" + std::move(message.prefix) + " ";
385
  res += message.command;
louiz’'s avatar
louiz’ committed
386 387
  for (const std::string& arg: message.arguments)
    {
louiz’'s avatar
:3  
louiz’ committed
388
      if (arg.find(" ") != std::string::npos ||
louiz’'s avatar
louiz’ committed
389
          (!arg.empty() && arg[0] == ':'))
louiz’'s avatar
louiz’ committed
390 391 392 393 394 395 396
        {
          res += " :" + arg;
          break;
        }
      res += " " + arg;
    }
  res += "\r\n";
397
  this->send_data(std::move(res));
louiz’'s avatar
louiz’ committed
398 399
}

louiz’'s avatar
louiz’ committed
400 401
void IrcClient::send_raw(const std::string& txt)
{
402
  log_debug("IRC SENDING (raw): (", this->get_hostname(), ") ", txt);
louiz’'s avatar
louiz’ committed
403 404 405
  this->send_data(txt + "\r\n");
}

louiz’'s avatar
louiz’ committed
406 407
void IrcClient::send_user_command(const std::string& username, const std::string& realname)
{
louiz’'s avatar
louiz’ committed
408
  this->send_message(IrcMessage("USER", {username, this->user_hostname, "ignored", realname}));
louiz’'s avatar
louiz’ committed
409 410 411 412 413 414 415
}

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

416 417 418 419 420
void IrcClient::send_pass_command(const std::string& password)
{
  this->send_message(IrcMessage("PASS", {password}));
}

louiz’'s avatar
louiz’ committed
421 422 423 424 425
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}));
}

426 427 428 429 430
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}));
}

431 432
void IrcClient::send_list_command()
{
433
  this->send_message(IrcMessage("LIST", {"*"}));
434 435
}

436 437 438 439 440
void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick)
{
  this->send_message(IrcMessage("INVITE", {nick, chan_name}));
}

441 442 443 444 445
void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
{
  this->send_message(IrcMessage("TOPIC", {chan_name, topic}));
}

446
void IrcClient::send_quit_command(const std::string& reason)
louiz’'s avatar
louiz’ committed
447
{
448
  this->send_message(IrcMessage("QUIT", {reason}));
louiz’'s avatar
louiz’ committed
449 450
}

451
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
louiz’'s avatar
louiz’ committed
452
{
louiz’'s avatar
louiz’ committed
453
  if (this->welcomed == false)
454 455 456 457 458 459
    {
      const auto it = std::find_if(begin(this->channels_to_join), end(this->channels_to_join),
                                   [&chan_name](const auto& pair) { return std::get<0>(pair) == chan_name; });
      if (it == end(this->channels_to_join))
        this->channels_to_join.emplace_back(chan_name, password);
    }
460 461
  else if (password.empty())
    this->send_message(IrcMessage("JOIN", {chan_name}));
louiz’'s avatar
louiz’ committed
462
  else
463
    this->send_message(IrcMessage("JOIN", {chan_name, password}));
464
  this->start();
465 466
}

louiz’'s avatar
louiz’ committed
467
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
468
{
louiz’'s avatar
louiz’ committed
469 470 471
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == false)
    {
472
      log_warning("Cannot send message to channel ", chan_name, ", it is not joined");
louiz’'s avatar
louiz’ committed
473 474
      return false;
    }
475 476 477 478 479 480 481 482 483
  // The max size is 512, taking into account the whole message, not just
  // the text we send.
  // This includes our own nick, username and host (because this will be
  // added by the server into our message), in addition to the basic
  // components of the message we send (command name, chan name, \r\n et)
  //  : + NICK + ! + USER + @ + HOST + <space> + PRIVMSG + <space> + CHAN + <space> + : + \r\n
  const auto line_size = 512 -
                         this->current_nick.size() - this->username.size() - this->own_host.size() -
                         ::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n");
484 485 486
  const auto lines = cut(body, line_size);
  for (const auto& line: lines)
    this->send_message(IrcMessage("PRIVMSG", {chan_name, line}));
louiz’'s avatar
louiz’ committed
487 488 489
  return true;
}

louiz’'s avatar
louiz’ committed
490
void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
491
{
492 493 494
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
louiz’'s avatar
louiz’ committed
495
      this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)}));
496 497
      pos += 400;
    }
498 499 500
  // 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
501 502
}

louiz’'s avatar
louiz’ committed
503 504
void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
{
louiz’'s avatar
louiz’ committed
505
  this->send_message(IrcMessage("PART", {chan_name, status_message}));
louiz’'s avatar
louiz’ committed
506 507 508 509 510 511 512 513
}

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
514 515
}

louiz’'s avatar
louiz’ committed
516 517 518 519
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
520 521
}

louiz’'s avatar
louiz’ committed
522
void IrcClient::on_pong(const IrcMessage&)
523 524 525
{
}

526 527 528 529 530
void IrcClient::send_ping_command()
{
  this->send_message(IrcMessage("PING", {"biboumi"}));
}

531 532 533 534 535
void IrcClient::forward_server_message(const IrcMessage& message)
{
  const std::string from = message.prefix;
  const std::string body = message.arguments[1];

536
  this->bridge.send_xmpp_message(this->hostname, from, body);
537
}
louiz’'s avatar
louiz’ committed
538

539 540 541 542 543 544
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];

545 546 547 548
  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 ;

549
  if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end())
550
    {
louiz’'s avatar
louiz’ committed
551
      // The notice is for us precisely.
552 553 554 555 556 557 558 559 560

      // 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)
561
          this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body,
562 563 564
                                     false);
        }
      else
565
        this->bridge.send_xmpp_message(this->hostname, from, body);
566
    }
567 568
  else
    {
569 570 571
      // 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
572
      IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 "s + body});
573 574 575 576
      this->on_channel_message(modified_message);
    }
}

louiz’'s avatar
louiz’ committed
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
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] != ')')
598 599 600 601
          {
            this->sorted_user_modes.push_back(token[i]);
            this->prefix_to_mode[token[j++]] = token[i++];
          }
louiz’'s avatar
louiz’ committed
602
      }
603 604 605 606 607
    else if (token.substr(0, 10) == "CHANTYPES=")
      {
        // Remove the default types, they apply only if no other value is
        // specified.
        this->chantypes.clear();
608
        size_t i = 10;
609 610 611
        while (i < token.size())
          this->chantypes.insert(token[i++]);
      }
louiz’'s avatar
louiz’ committed
612 613
  }
}
614

louiz’'s avatar
louiz’ committed
615 616 617 618
void IrcClient::on_server_myinfo(const IrcMessage&)
{
}

619 620
void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
{
621
  this->bridge.send_xmpp_message(this->hostname, from, message);
622 623
}

624 625
void IrcClient::set_and_forward_user_list(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
626
  const std::string chan_name = utils::tolower(message.arguments[2]);
627 628 629 630
  IrcChannel* channel = this->get_channel(chan_name);
  std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
  for (const std::string& nick: nicks)
    {
631
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
632 633
      if (user->nick != channel->get_self()->nick)
        {
634
          this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
635 636 637 638 639
        }
      else
        {
          // we now know the modes of self, so copy the modes into self
          channel->get_self()->modes = user->modes;
640 641 642 643
        }
    }
}

louiz’'s avatar
louiz’ committed
644
void IrcClient::on_channel_join(const IrcMessage& message)
645
{
louiz’'s avatar
louiz’ committed
646
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
647 648 649 650 651
  IrcChannel* channel;
  if (chan_name.empty())
    channel = &this->dummy_channel;
  else
    channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
652 653
  const std::string nick = message.prefix;
  if (channel->joined == false)
654
    channel->set_self(nick);
louiz’'s avatar
louiz’ committed
655 656
  else
    {
657
      const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
658
      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
659
    }
660 661
}

louiz’'s avatar
louiz’ committed
662 663 664 665 666
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
667 668
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
louiz’'s avatar
louiz’ committed
669
  const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
670
  bool muc = true;
louiz’'s avatar
louiz’ committed
671
  if (!this->get_channel(iid.get_local())->joined)
louiz’'s avatar
louiz’ committed
672
    {
673
      iid.type = Iid::Type::User;
louiz’'s avatar
louiz’ committed
674
      iid.set_local(nick);
louiz’'s avatar
louiz’ committed
675 676
      muc = false;
    }
louiz’'s avatar
louiz’ committed
677
  else
678
    iid.type = Iid::Type::Channel;
679 680 681
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")
682
        this->bridge.send_message(iid, nick,
louiz’'s avatar
louiz’ committed
683
                  "/me"s + body.substr(7, body.size() - 8), muc);
684
      else if (body.substr(1, 8) == "VERSION\01")
685
        this->bridge.send_iq_version_request(nick, this->hostname);
686
      else if (body.substr(1, 5) == "PING ")
687
        this->bridge.send_xmpp_ping_request(utils::tolower(nick), this->hostname,
688
                                             body.substr(6, body.size() - 7));
689 690
    }
  else
691
    this->bridge.send_message(iid, nick, body, muc);
louiz’'s avatar
louiz’ committed
692 693
}

694 695 696 697 698 699 700 701 702 703 704 705
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
706
void IrcClient::empty_motd(const IrcMessage&)
707 708 709 710
{
  this->motd.erase();
}

711 712 713 714 715 716 717 718
void IrcClient::on_invited(const IrcMessage& message)
{
  const std::string& chan_name = message.arguments[2];
  const std::string& invited_nick = message.arguments[1];

  this->bridge.send_xmpp_message(this->hostname, "", invited_nick + " has been invited to " + chan_name);
}

louiz’'s avatar
louiz’ committed
719 720
void IrcClient::on_empty_topic(const IrcMessage& message)
{
721
  const std::string chan_name = utils::tolower(message.arguments[1]);
722
  log_debug("empty topic for ", chan_name);
louiz’'s avatar
louiz’ committed
723 724 725 726 727
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel)
    channel->topic.clear();
}

728 729 730 731 732 733 734 735 736
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
737
void IrcClient::send_motd(const IrcMessage&)
738
{
739
  this->bridge.send_xmpp_message(this->hostname, "", this->motd);
740 741
}

742 743
void IrcClient::on_topic_received(const IrcMessage& message)
{
744
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]);
745
  IrcUser author(message.prefix);
746
  IrcChannel* channel = this->get_channel(chan_name);
747
  channel->topic = message.arguments[message.arguments.size() - 1];
748
  channel->topic_author = author.nick;
louiz’'s avatar
louiz’ committed
749
  if (channel->joined)
750 751 752 753 754 755 756 757 758
    this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
}

void IrcClient::on_topic_who_time_received(const IrcMessage& message)
{
  IrcUser author(message.arguments[2]);
  const std::string chan_name = utils::tolower(message.arguments[1]);
  IrcChannel* channel = this->get_channel(chan_name);
  channel->topic_author = author.nick;
759 760 761 762
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
763
  const std::string chan_name = utils::tolower(message.arguments[1]);
764
  IrcChannel* channel = this->get_channel(chan_name);
765
  channel->joined = true;
766 767 768
  this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
                              channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
  this->bridge.send_room_history(this->hostname, chan_name);
769
  this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
770
}
louiz’'s avatar
louiz’ committed
771

772 773 774 775 776 777 778 779 780 781 782 783
void IrcClient::on_own_host_received(const IrcMessage& message)
{
  this->own_host = message.arguments[1];
  const std::string from = message.prefix;
  if (message.arguments.size() >= 3)
    this->bridge.send_xmpp_message(this->hostname, from,
                                   this->own_host + " " + message.arguments[2]);
  else
    this->bridge.send_xmpp_message(this->hostname, from, this->own_host +
                                                         " is now your displayed host");
}

784 785 786 787 788 789 790
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);
}

791 792 793 794
void IrcClient::on_nickname_conflict(const IrcMessage& message)
{
  const std::string nickname = message.arguments[1];
  this->on_generic_error(message);
795
  for (const auto& pair: this->channels)
796 797
  {
    Iid iid;
798
    iid.set_local(pair.first);
louiz’'s avatar
louiz’ committed
799
    iid.set_server(this->hostname);
800
    iid.type = Iid::Type::Channel;
801
    this->bridge.send_nickname_conflict_error(iid, nickname);
802 803 804
  }
}

805 806 807 808 809 810 811
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);
812
  for (const auto& pair: this->channels)
813 814
  {
    Iid iid;
815
    iid.set_local(pair.first);
816
    iid.set_server(this->hostname);
817
    iid.type = Iid::Type::Channel;
818
    this->bridge.send_presence_error(iid, nickname,
819 820 821 822
                                      "cancel", "not-acceptable",
                                      "", txt);
  }
}
823 824 825 826 827 828 829
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);
}

830 831 832 833 834 835
void IrcClient::on_useronchannel(const IrcMessage& message)
{
  this->send_gateway_message(message.arguments[1] + " " + message.arguments[3] + " "
                             + message.arguments[2]);
}

louiz’'s avatar
louiz’ committed
836 837 838 839
void IrcClient::on_welcome_message(const IrcMessage& message)
{
  this->current_nick = message.arguments[0];
  this->welcomed = true;
840
#ifdef USE_DATABASE
841
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
842 843 844 845
                                                  this->get_hostname());
  if (!options.afterConnectionCommand.value().empty())
    this->send_raw(options.afterConnectionCommand.value());
#endif
846 847
  // Install a repeated events to regularly send a PING
  TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
848
                                                      "PING"s + this->hostname + this->bridge.get_jid()));
849 850
  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
851
  this->channels_to_join.clear();
louiz’'s avatar
louiz’ committed
852 853 854 855 856 857 858 859 860 861 862 863
  // 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
864
}
louiz’'s avatar
louiz’ committed
865 866 867

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
868
  const std::string chan_name = message.arguments[0];
louiz’'s avatar
louiz’ committed
869
  IrcChannel* channel = this->get_channel(chan_name);
870 871
  if (!channel->joined)
    return ;
louiz’'s avatar
louiz’ committed
872 873 874 875 876 877 878 879 880
  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
881 882
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
883
      iid.type = Iid::Type::Channel;
louiz’'s avatar
louiz’ committed
884 885
      bool self = channel->get_self()->nick == nick;
      if (self)
louiz’'s avatar
louiz’ committed
886
      {
louiz’'s avatar
louiz’ committed
887
        channel->joined = false;
louiz’'s avatar
louiz’ committed
888
        this->channels.erase(utils::tolower(chan_name));
louiz’'s avatar
louiz’ committed
889 890 891
        // channel pointer is now invalid
        channel = nullptr;
      }
892
      this->bridge.send_muc_leave(iid, std::move(nick), txt, self);
louiz’'s avatar
louiz’ committed
893 894
    }
}
louiz’'s avatar
louiz’ committed
895

louiz’'s avatar
louiz’ committed
896 897 898 899
void IrcClient::on_error(const IrcMessage& message)
{
  const std::string leave_message = message.arguments[0];
  // The user is out of all the channels
900
  for (const auto& pair: this->channels)
louiz’'s avatar
louiz’ committed
901 902
  {
    Iid iid;
903
    iid.set_local(pair.first);
louiz’'s avatar
louiz’ committed
904
    iid.set_server(this->hostname);
905
    iid.type = Iid::Type::Channel;
906
    IrcChannel* channel = pair.second.get();
907 908
    if (!channel->joined)
      continue;
louiz’'s avatar
louiz’ committed
909
    std::string own_nick = channel->get_self()->nick;
louiz’'s avatar
louiz’ committed
910
    this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true);
louiz’'s avatar
louiz’ committed
911
  }
louiz’'s avatar
louiz’ committed
912
  this->channels.clear();
louiz’'s avatar
louiz’ committed
913
  this->send_gateway_message("ERROR: "s + leave_message);
louiz’'s avatar
louiz’ committed
914 915
}

louiz’'s avatar
louiz’ committed
916 917 918 919 920
void IrcClient::on_quit(const IrcMessage& message)
{
  std::string txt;
  if (message.arguments.size() >= 1)
    txt = message.arguments[0];
921
  for (const auto& pair: this->channels)
louiz’'s avatar
louiz’ committed
922
    {
923 924
      const std::string& chan_name = pair.first;
      IrcChannel* channel = pair.second.get();
louiz’'s avatar
louiz’ committed
925 926 927 928 929 930
      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
931 932
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
933
          iid.type = Iid::Type::Channel;
louiz’'s avatar
louiz’ committed
934
          this->bridge.send_muc_leave(iid, std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
935 936 937
        }
    }
}
louiz’'s avatar
louiz’ committed
938 939 940

void IrcClient::on_nick(const IrcMessage& message)
{
941 942
  const std::string new_nick = IrcUser(message.arguments[0]).nick;
  const std::string current_nick = IrcUser(message.prefix).nick;
louiz’'s avatar
louiz’ committed
943
  const auto change_nick_func = [this, &new_nick, &current_nick](const std::string& chan_name, const IrcChannel* channel)
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969
  {
    IrcUser* user;
    if (channel->get_self() && channel->get_self()->nick == current_nick)
      user = channel->get_self();
    else
      user = channel->find_user(current_nick);
    if (user)
      {
        std::string old_nick = user->nick;
        Iid iid(chan_name, this->hostname, Iid::Type::Channel);
        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);
        user->nick = new_nick;
        if (self)
          {
            channel->get_self()->nick = new_nick;
            this->current_nick = new_nick;
          }
      }
  };

  if (this->get_dummy_channel().joined)
    {
      change_nick_func("", &this->get_dummy_channel());
    }
970
  for (const auto& pair: this->channels)
louiz’'s avatar
louiz’ committed
971
    {
972
      change_n