irc_client.cpp 48.1 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
#include <irc/irc_client.hpp>
#include <bridge/bridge.hpp>
#include <irc/irc_user.hpp>
8
#include <utils/base64.hpp>
9

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

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

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

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

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

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

static const std::unordered_map<std::string,
37
                                std::pair<IrcCallback, std::pair<std::size_t, std::size_t>>> irc_callbacks = {
38 39 40
  {"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
41
  {"004", {&IrcClient::on_server_myinfo, {4, 0}}},
42 43 44 45 46 47 48
  {"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
49 50
  {"RPL_NOTOPIC", {&IrcClient::on_empty_topic, {0, 0}}},
  {"331", {&IrcClient::on_empty_topic, {0, 0}}},
51
  {"341", {&IrcClient::on_invited, {3, 0}}},
52 53 54 55 56 57 58 59 60 61 62
  {"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}}},
63 64
  {"333", {&IrcClient::on_topic_who_time_received, {4, 0}}},
  {"RPL_TOPICWHOTIME", {&IrcClient::on_topic_who_time_received, {4, 0}}},
65
  {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
66 67
  {"367", {&IrcClient::on_banlist, {3, 0}}},
  {"368", {&IrcClient::on_banlist_end, {3, 0}}},
68
  {"396", {&IrcClient::on_own_host_received, {2, 0}}},
69 70 71
  {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
  {"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
  {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
72
  {"443", {&IrcClient::on_useronchannel, {3, 0}}},
73
  {"475", {&IrcClient::on_channel_bad_key, {3, 0}}},
74
  {"ERR_USERONCHANNEL", {&IrcClient::on_useronchannel, {3, 0}}},
75 76 77 78 79 80 81 82 83
  {"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
84
  {"INVITE", {&IrcClient::on_invite, {2, 0}}},
85
  {"CAP", {&IrcClient::on_cap, {3, 0}}},
86
#ifdef WITH_SASL
87 88
  {"AUTHENTICATE", {&IrcClient::on_authenticate, {1, 0}}},
  {"900", {&IrcClient::on_sasl_login, {3, 0}}},
89 90 91 92 93 94 95
  {"902", {&IrcClient::on_sasl_failure, {2, 0}}},
  {"903", {&IrcClient::on_sasl_success, {0, 0}}},
  {"904", {&IrcClient::on_sasl_failure, {2, 0}}},
  {"905", {&IrcClient::on_sasl_failure, {2, 0}}},
  {"906", {&IrcClient::on_sasl_failure, {2, 0}}},
  {"907", {&IrcClient::on_sasl_failure, {2, 0}}},
  {"908", {&IrcClient::on_sasl_failure, {2, 0}}},
96
#endif
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  {"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}}},
};
144

145 146 147
IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
                     std::string nickname, std::string username,
                     std::string realname, std::string user_hostname,
148
                     Bridge& bridge):
149
  TCPClientSocketHandler(poller),
150
  hostname(hostname),
151 152 153 154
  user_hostname(std::move(user_hostname)),
  username(std::move(username)),
  realname(std::move(realname)),
  current_nick(std::move(nickname)),
louiz’'s avatar
louiz’ committed
155
  bridge(bridge),
louiz’'s avatar
louiz’ committed
156
  welcomed(false),
157
  chanmodes({"", "", "", ""}),
158
  chantypes({'#', '&'}),
159
  tokens_bucket(this->get_throttle_limit(), 1s, [this]() {
160 161 162 163 164 165
    if (message_queue.empty())
      return true;
    this->actual_send(std::move(this->message_queue.front()));
    this->message_queue.pop_front();
    return false;
  }, "TokensBucket" + this->hostname + this->bridge.get_jid())
166
{
167
#ifdef USE_DATABASE
168
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
169
                                                  this->get_hostname());
170
  std::vector<std::string> ports = utils::split(options.col<Database::Ports>(), ';', false);
171 172 173
  for (auto it = ports.rbegin(); it != ports.rend(); ++it)
    this->ports_to_try.emplace(*it, false);
# ifdef BOTAN_FOUND
174
  ports = utils::split(options.col<Database::TlsPorts>(), ';', false);
175 176 177 178 179
  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
180
  this->ports_to_try.emplace("6667", false); // standard non-encrypted port
181
# ifdef BOTAN_FOUND
louiz’'s avatar
louiz’ committed
182 183
  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
184 185
# endif // BOTAN_FOUND
#endif // USE_DATABASE
186 187 188 189
}

IrcClient::~IrcClient()
{
190 191
  // This event may or may not exist (if we never got connected, it
  // doesn't), but it's ok
192
  TimedEventsManager::instance().cancel("PING" + this->hostname + this->bridge.get_jid());
193
  TimedEventsManager::instance().cancel("TokensBucket" + this->hostname + this->bridge.get_jid());
194 195
}

196 197
void IrcClient::start()
{
198 199
  if (this->is_connecting() || this->is_connected())
    return;
200 201 202 203 204
  if (this->ports_to_try.empty())
    {
      this->bridge.send_xmpp_message(this->hostname, "", "Can not connect to IRC server: no port specified.");
      return;
    }
louiz’'s avatar
louiz’ committed
205 206 207 208
  std::string port;
  bool tls;
  std::tie(port, tls) = this->ports_to_try.top();
  this->ports_to_try.pop();
louiz’'s avatar
louiz’ committed
209
  this->bind_addr = Config::get("outgoing_bind", "");
210
  std::string address = this->hostname;
louiz’'s avatar
louiz’ committed
211

212
#ifdef USE_DATABASE
213 214
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
                                                  this->get_hostname());
215
# ifdef BOTAN_FOUND
216
  this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
217
# endif
218 219
  if (Config::get("fixed_irc_server", "").empty() &&
      !options.col<Database::Address>().empty())
220
    address = options.col<Database::Address>();
221
#endif
222 223 224 225
  this->bridge.send_xmpp_message(this->hostname, "", "Connecting to " +
                                  address + ":" + port + " (" +
                                  (tls ? "encrypted" : "not encrypted") + ")");
  this->connect(address, port, tls);
226 227 228 229
}

void IrcClient::on_connection_failed(const std::string& reason)
{
230
  this->bridge.send_xmpp_message(this->hostname, "",
231
                                  "Connection failed: " + reason);
232 233 234 235 236

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

louiz’'s avatar
louiz’ committed
237
  if (this->ports_to_try.empty())
238
    {
louiz’'s avatar
louiz’ committed
239
      // Send an error message for all room that the user wanted to join
240
      for (const auto& tuple: this->channels_to_join)
louiz’'s avatar
louiz’ committed
241
        {
242
          Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
243
          this->bridge.send_presence_error(iid, this->current_nick,
244 245
                                            "cancel", "item-not-found",
                                            "", reason);
louiz’'s avatar
louiz’ committed
246
        }
247
      this->channels_to_join.clear();
248
    }
louiz’'s avatar
louiz’ committed
249 250
  else                          // try the next port
    this->start();
251 252
}

louiz’'s avatar
louiz’ committed
253 254
void IrcClient::on_connected()
{
louiz’'s avatar
louiz’ committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
  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())
                                         {
279
                                           this->on_connection_close("Could not resolve hostname " + this->user_hostname +
louiz’'s avatar
louiz’ committed
280 281 282 283 284 285 286 287
                                                                     ": " + error_msg);
                                           this->send_quit_command("");
                                         }
                                     });
          return;
        }
    }

288 289 290
  this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");

  this->capabilities["multi-prefix"] = {[]{}, []{}};
louiz’'s avatar
louiz’ committed
291

292
#ifdef USE_DATABASE
293
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
294
                                                  this->get_hostname());
295 296 297 298

  const auto& server_password = options.col<Database::Pass>();

  if (!server_password.empty())
299
    this->send_pass_command(options.col<Database::Pass>());
300
#endif
301

302 303
#ifdef WITH_SASL
  const auto& sasl_password = options.col<Database::SaslPassword>();
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
  if (!sasl_password.empty())
    {
      this->capabilities["sasl"] = {
          [this]
          {
            this->send_message({"AUTHENTICATE", {"PLAIN"}});
            log_warning("negociating SASL now...");
          },
          []
          {
            log_warning("SASL not supported by the server, disconnecting.");
          }
      };
      this->sasl_state = SaslState::needed;
    }
319
#endif
320

321 322 323 324
  {
    for (const auto &pair : this->capabilities)
      this->send_message({ "CAP", {"REQ", pair.first}});
  }
325

326
  this->send_nick_command(this->current_nick);
327
#ifdef USE_DATABASE
328 329
  if (Config::get("realname_customization", "true") == "true")
    {
330 331 332 333
      if (!options.col<Database::Username>().empty())
        this->username = options.col<Database::Username>();
      if (!options.col<Database::Realname>().empty())
        this->realname = options.col<Database::Realname>();
334 335
      this->send_user_command(username, realname);
    }
336 337
  else
#endif
338
  this->send_user_command(this->username, this->realname);
louiz’'s avatar
louiz’ committed
339 340
}

341
void IrcClient::on_connection_close(const std::string& error_msg)
342
{
343
  std::string message = "Connection closed";
344 345
  if (!error_msg.empty())
    message += ": " + error_msg;
346 347
  else
    message += ".";
348 349
  const IrcMessage error{"ERROR", {message}};
  this->on_error(error);
350
  log_warning(message);
351
  this->bridge.on_irc_client_disconnected(this->get_hostname());
352 353
}

louiz’'s avatar
louiz’ committed
354
IrcChannel* IrcClient::get_channel(const std::string& n)
355
{
louiz’'s avatar
louiz’ committed
356
  const std::string name = utils::tolower(n);
357 358 359 360 361 362
  try
    {
      return this->channels.at(name).get();
    }
  catch (const std::out_of_range& exception)
    {
363 364 365 366 367 368 369 370 371
      return this->channels.emplace(name, std::make_unique<IrcChannel>()).first->second.get();
    }
}

const IrcChannel* IrcClient::find_channel(const std::string& n) const
{
  const std::string name = utils::tolower(n);
  try
    {
372 373
      return this->channels.at(name).get();
    }
374 375 376 377
  catch (const std::out_of_range& exception)
    {
      return nullptr;
    }
378 379
}

louiz’'s avatar
louiz’ committed
380 381
bool IrcClient::is_channel_joined(const std::string& name)
{
louiz’'s avatar
louiz’ committed
382 383
  IrcChannel* channel = this->get_channel(name);
  return channel->joined;
louiz’'s avatar
louiz’ committed
384 385
}

louiz’'s avatar
louiz’ committed
386 387 388 389 390
std::string IrcClient::get_own_nick() const
{
  return this->current_nick;
}

391
void IrcClient::parse_in_buffer(const size_t)
392
{
louiz’'s avatar
louiz’ committed
393 394 395 396 397 398
  while (true)
    {
      auto pos = this->in_buf.find("\r\n");
      if (pos == std::string::npos)
        break ;
      IrcMessage message(this->in_buf.substr(0, pos));
399
      this->consume_in_buffer(pos + 2);
400
      log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
401 402 403

      // Call the standard callback (if any), associated with the command
      // name that we just received.
404 405
      auto it = irc_callbacks.find(message.command);
      if (it != irc_callbacks.end())
406
        {
407 408
          const auto& limits = it->second.second;
          // Check that the Message is well formed before actually calling
409 410 411 412 413 414
          // the callback.
          const auto args_size = message.arguments.size();
          const auto min = limits.first;
          const auto max = limits.second;
          if (args_size < min ||
              (max > 0 && args_size > max))
415
            log_warning("Invalid number of arguments for IRC command “", message.command,
416
                        "”: ", args_size);
417 418 419 420 421 422
          else
            {
              const auto& cb = it->second.first;
              try {
                (this->*(cb))(message);
              } catch (const std::exception& e) {
423
                log_error("Unhandled exception: ", e.what());
424 425
              }
            }
426
        }
427
      else
428
        {
429
          log_info("No handler for command ", message.command,
430 431 432
                   ", forwarding the arguments to the user");
          this->on_unknown_message(message);
        }
433
      // Try to find a waiting_iq, which response will be triggered by this IrcMessage
434
      this->bridge.trigger_on_irc_message(this->hostname, message);
louiz’'s avatar
louiz’ committed
435 436 437
    }
}

louiz’'s avatar
louiz’ committed
438
void IrcClient::actual_send(std::pair<IrcMessage, MessageCallback>&& message_pair)
louiz’'s avatar
louiz’ committed
439
{
440 441
  const IrcMessage& message = message_pair.first;
  const MessageCallback& callback = message_pair.second;
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
   log_debug("IRC SENDING: (", this->get_hostname(), ") ", message);
    std::string res;
    if (!message.prefix.empty())
      res += ":" + message.prefix + " ";
    res += message.command;
    for (const std::string& arg: message.arguments)
      {
        if (arg.find(' ') != std::string::npos
            || (!arg.empty() && arg[0] == ':'))
          {
            res += " :" + arg;
            break;
          }
        res += " " + arg;
      }
    res += "\r\n";
    this->send_data(std::move(res));
459 460 461

    if (callback)
      callback(this, message);
462 463
 }

464
void IrcClient::send_message(IrcMessage message, MessageCallback callback, bool throttle)
465
{
466
  auto message_pair = std::make_pair(std::move(message), std::move(callback));
467
  if (this->tokens_bucket.use_token() || !throttle)
468
    this->actual_send(std::move(message_pair));
469
  else
470
    message_queue.push_back(std::move(message_pair));
louiz’'s avatar
louiz’ committed
471 472
}

louiz’'s avatar
louiz’ committed
473 474
void IrcClient::send_raw(const std::string& txt)
{
475
  log_debug("IRC SENDING (raw): (", this->get_hostname(), ") ", txt);
louiz’'s avatar
louiz’ committed
476 477 478
  this->send_data(txt + "\r\n");
}

louiz’'s avatar
louiz’ committed
479 480
void IrcClient::send_user_command(const std::string& username, const std::string& realname)
{
louiz’'s avatar
louiz’ committed
481
  this->send_message(IrcMessage("USER", {username, this->user_hostname, "ignored", realname}));
louiz’'s avatar
louiz’ committed
482 483 484 485 486 487 488
}

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

489 490 491 492 493
void IrcClient::send_pass_command(const std::string& password)
{
  this->send_message(IrcMessage("PASS", {password}));
}

louiz’'s avatar
louiz’ committed
494 495 496 497 498
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}));
}

499 500 501 502 503
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}));
}

504 505
void IrcClient::send_list_command()
{
506
  this->send_message(IrcMessage("LIST", {"*"}));
507 508
}

509 510 511 512 513
void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick)
{
  this->send_message(IrcMessage("INVITE", {nick, chan_name}));
}

514 515 516 517 518
void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
{
  this->send_message(IrcMessage("TOPIC", {chan_name, topic}));
}

519
void IrcClient::send_quit_command(const std::string& reason)
louiz’'s avatar
louiz’ committed
520
{
521
  this->send_message(IrcMessage("QUIT", {reason}), {}, false);
louiz’'s avatar
louiz’ committed
522 523
}

524
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
louiz’'s avatar
louiz’ committed
525
{
louiz’'s avatar
louiz’ committed
526
  if (!this->welcomed)
527 528 529 530 531 532
    {
      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);
    }
533 534
  else if (password.empty())
    this->send_message(IrcMessage("JOIN", {chan_name}));
louiz’'s avatar
louiz’ committed
535
  else
536
    this->send_message(IrcMessage("JOIN", {chan_name, password}));
537
  this->start();
538 539
}

540 541
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body,
                                     MessageCallback callback)
542
{
louiz’'s avatar
louiz’ committed
543
  IrcChannel* channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
544
  if (!channel->joined)
louiz’'s avatar
louiz’ committed
545
    {
546
      log_warning("Cannot send message to channel ", chan_name, ", it is not joined");
louiz’'s avatar
louiz’ committed
547 548
      return false;
    }
549 550
  // The max size is 512, taking into account the whole message, not just
  // the text we send.
551 552 553
  // This includes our own nick, constants for username and host (because these
  // are notoriously hard to know what the server will use), in addition to the basic
  // components of the message we send (command name, chan name, \r\n etc.)
554
  //  : + NICK + ! + USER + @ + HOST + <space> + PRIVMSG + <space> + CHAN + <space> + : + \r\n
555 556 557 558
  // 63 is the maximum hostname length defined by the protocol.  10 seems to be
  // the username limit.
  constexpr auto max_username_size = 10;
  constexpr auto max_hostname_size = 63;
559
  const auto line_size = 512 -
560
                         this->current_nick.size() - max_username_size - max_hostname_size -
561
                         ::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n");
562 563
  const auto lines = cut(body, line_size);
  for (const auto& line: lines)
564
    this->send_message(IrcMessage("PRIVMSG", {chan_name, line}), callback);
louiz’'s avatar
louiz’ committed
565 566 567
  return true;
}

louiz’'s avatar
louiz’ committed
568
void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type)
569
{
570 571 572
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
louiz’'s avatar
louiz’ committed
573
      this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)}));
574 575
      pos += 400;
    }
576 577 578
  // 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);
579 580
}

581 582
void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
{
583
  this->send_message(IrcMessage("PART", {chan_name, status_message}));
584 585 586 587 588 589 590 591
}

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

louiz’'s avatar
louiz’ committed
594 595 596 597
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
598 599
}

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

604 605 606 607 608
void IrcClient::send_ping_command()
{
  this->send_message(IrcMessage("PING", {"biboumi"}));
}

609 610 611
void IrcClient::forward_server_message(const IrcMessage& message)
{
  const std::string from = message.prefix;
612 613 614
  std::string body;
  for (auto it = std::next(message.arguments.begin()); it != message.arguments.end(); ++it)
    body += *it + ' ';
615

616
  this->bridge.send_xmpp_message(this->hostname, from, body);
617
}
louiz’'s avatar
louiz’ committed
618

619 620 621
void IrcClient::on_notice(const IrcMessage& message)
{
  std::string from = message.prefix;
622
  std::string to = message.arguments[0];
623 624
  const std::string body = message.arguments[1];

625 626 627 628 629 630 631 632 633
  // Handle notices starting with [#channame] as if they were sent to that channel
  if (body.size() > 3 && body[0] == '[')
    {
      const auto chan_prefix = body[1];
      auto end = body.find(']');
      if (this->chantypes.find(chan_prefix) != this->chantypes.end() && end != std::string::npos)
        to = body.substr(1, end - 1);
    }

634 635 636 637
  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 ;

638
  if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end())
639
    {
louiz’'s avatar
louiz’ committed
640
      // The notice is for us precisely.
641 642 643 644 645 646 647 648 649

      // 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)
650
          this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body,
651 652 653
                                     false);
        }
      else
654
        this->bridge.send_xmpp_message(this->hostname, from, body);
655
    }
656 657
  else
    {
658 659 660
      // 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
661
      IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 " + body});
662 663 664 665
      this->on_channel_message(modified_message);
    }
}

louiz’'s avatar
louiz’ committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
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] != ')')
687 688 689 690
          {
            this->sorted_user_modes.push_back(token[i]);
            this->prefix_to_mode[token[j++]] = token[i++];
          }
louiz’'s avatar
louiz’ committed
691
      }
692 693 694 695 696
    else if (token.substr(0, 10) == "CHANTYPES=")
      {
        // Remove the default types, they apply only if no other value is
        // specified.
        this->chantypes.clear();
697
        size_t i = 10;
698 699 700
        while (i < token.size())
          this->chantypes.insert(token[i++]);
      }
louiz’'s avatar
louiz’ committed
701 702
  }
}
703

louiz’'s avatar
louiz’ committed
704 705 706 707
void IrcClient::on_server_myinfo(const IrcMessage&)
{
}

708 709
void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
{
710
  this->bridge.send_xmpp_message(this->hostname, from, message);
711 712
}

713 714
void IrcClient::set_and_forward_user_list(const IrcMessage& message)
{
715
  const std::string chan_name = utils::tolower(message.arguments[2]);
716
  IrcChannel* channel = this->get_channel(chan_name);
717 718 719 720 721
  if (channel->joined)
    {
      this->forward_server_message(message);
      return;
    }
722 723 724
  std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
  for (const std::string& nick: nicks)
    {
725 726 727 728
      // Just create this dummy user to parse and get its modes
      IrcUser tmp_user{nick, this->prefix_to_mode};
      // Does this concern ourself
      if (channel->get_self() && channel->find_user(tmp_user.nick) == channel->get_self())
729
        {
730 731
          // We now know our own modes, that’s all.
          channel->get_self()->modes = tmp_user.modes;
732 733
        }
      else
734 735 736
        { // Otherwise this is a new user
          const IrcUser *user = channel->add_user(nick, this->prefix_to_mode);
          this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
737 738 739 740
        }
    }
}

741
void IrcClient::on_channel_join(const IrcMessage& message)
742
{
743
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
744
  IrcChannel* channel;
745
  channel = this->get_channel(chan_name);
746
  const std::string nick = message.prefix;
747
  IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
748
  if (channel->joined == false)
749
    channel->set_self(user);
750
  else
751
    this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
752 753
}

louiz’'s avatar
louiz’ committed
754 755 756 757 758
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
759 760
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
louiz’'s avatar
louiz’ committed
761
  const std::string body = message.arguments[1];
762
  bool muc = true;
louiz’'s avatar
louiz’ committed
763
  if (!this->get_channel(iid.get_local())->joined)
764
    {
765
      iid.type = Iid::Type::User;
louiz’'s avatar
louiz’ committed
766
      iid.set_local(nick);
767 768
      muc = false;
    }
louiz’'s avatar
louiz’ committed
769
  else
770
    iid.type = Iid::Type::Channel;
771 772 773
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")
774
        this->bridge.send_message(iid, nick,
775
                  "/me" + body.substr(7, body.size() - 8), muc);
776
      else if (body.substr(1, 8) == "VERSION\01")
777
        this->bridge.send_iq_version_request(nick, this->hostname);
778
      else if (body.substr(1, 5) == "PING ")
779
        this->bridge.send_xmpp_ping_request(utils::tolower(nick), this->hostname,
780
                                             body.substr(6, body.size() - 7));
781 782
    }
  else
783
    this->bridge.send_message(iid, nick, body, muc);
louiz’'s avatar
louiz’ committed
784 785
}

786 787 788 789 790 791 792 793 794 795 796 797
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
798
void IrcClient::empty_motd(const IrcMessage&)
799 800 801 802
{
  this->motd.erase();
}

803 804 805 806 807 808 809 810
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
811 812
void IrcClient::on_empty_topic(const IrcMessage& message)
{
813
  const std::string chan_name = utils::tolower(message.arguments[1]);
814
  log_debug("empty topic for ", chan_name);
louiz’'s avatar
louiz’ committed
815 816 817 818 819
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel)
    channel->topic.clear();
}

820 821 822 823 824 825 826 827 828
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
829
void IrcClient::send_motd(const IrcMessage&)
830
{
831
  this->bridge.send_xmpp_message(this->hostname, "", this->motd);
832 833
}

834 835
void IrcClient::on_topic_received(const IrcMessage& message)
{
836
  const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]);
837
  IrcUser author(message.prefix);
838
  IrcChannel* channel = this->get_channel(chan_name);
839
  channel->topic = message.arguments[message.arguments.size() - 1];
840
  channel->topic_author = author.nick;
louiz’'s avatar
louiz’ committed
841
  if (channel->joined)
842 843 844 845 846 847 848 849 850
    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;
851 852 853 854
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
855
  const std::string chan_name = utils::tolower(message.arguments[1]);
856
  IrcChannel* channel = this->get_channel(chan_name);
857 858 859 860 861 862 863 864 865 866
  if (chan_name == "*" || channel->joined)
    {
      this->forward_server_message(message);
      return;
    }
  if (!channel->get_self())
    {
      log_error("End of NAMES list but we never received our own nick.");
      return;
    }
867
  channel->joined = true;
868 869
  this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
                              channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
870
  this->bridge.send_room_history(this->hostname, chan_name, this->history_limit);
871
  this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
872
}
louiz’'s avatar
louiz’ committed
873

874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
void IrcClient::on_banlist(const IrcMessage& message)
{
  const std::string chan_name = utils::tolower(message.arguments[1]);
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined)
    {
      Iid iid;
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.type = Iid::Type::Channel;
      std::string body{message.arguments[2] + " banned"};
      if (message.arguments.size() >= 4)
        {
          IrcUser by(message.arguments[3], this->prefix_to_mode);
          body += " by " + by.nick;
        }
      if (message.arguments.size() >= 5)
        body += " on " + message.arguments[4];

      this->bridge.send_message(iid, "", body, true);
    }
}

void IrcClient::on_banlist_end(const IrcMessage& message)
{
  const std::string chan_name = utils::tolower(message.arguments[1]);
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined)
    {
      Iid iid;
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.type = Iid::Type::Channel;
      this->bridge.send_message(iid, "", message.arguments[2], true);
    }
}

911 912 913 914 915 916 917 918 919 920 921 922
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");
}

923 924 925 926 927 928 929
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);
}

930 931 932 933
void IrcClient::on_nickname_conflict(const IrcMessage& message)
{
  const std::string nickname = message.arguments[1];
  this->on_generic_error(message);
934
  for (const auto& pair: this->channels)
935 936
  {
    Iid iid;
937
    iid.set_local(pair.first);
louiz’'s avatar
louiz’ committed
938
    iid.set_server(this->hostname);
939
    iid.type = Iid::Type::Channel;
940
    this->bridge.send_nickname_conflict_error(iid, nickname);
941 942 943
  }
}

944 945 946 947 948 949 950
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);
951
  for (const auto& pair: this->channels)
952 953
  {
    Iid iid;
954
    iid.set_local(pair.first);
955
    iid.set_server(this->hostname);
956
    iid.type = Iid::Type::Channel;
957
    this->bridge.send_presence_error(iid, nickname,
958 959 960 961
                                      "cancel", "not-acceptable",
                                      "", txt);
  }
}
962 963 964 965 966 967 968
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);
}

969 970 971 972 973 974
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
975 976 977 978
void IrcClient::on_welcome_message(const IrcMessage& message)
{
  this->current_nick = message.arguments[0];
  this->welcomed = true;
979
#ifdef USE_DATABASE
980
  auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
981
                                                  this->get_hostname());
982 983 984
  const auto commands = Database::get_after_connection_commands(options);
  for (const auto& command: commands)
    this->send_raw(command.col<Database::AfterConnectionCommand>());
985
#endif
986 987
  // Install a repeated events to regularly send a PING
  TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
988
                                                      "PING" + this->hostname + this->bridge.get_jid()));
989 990 991 992
  std::string channels{};
  std::string channels_with_key{};
  std::string keys{};

993
  for (const auto& tuple: this->channels_to_join)
994 995 996 997 998 999 1000
    {
      const auto& chan = std::get<0>(tuple);
      const auto& key = std::get<1>(tuple);
      if (chan.empty())
        continue;
      if (!key.empty())
        {
1001 1002 1003 1004 1005 1006
          if (keys.size() + channels_with_key.size() >= 300)
            { // Arbitrary size, to make sure we never send more than 512
              this->send_join_command(channels_with_key, keys);
              channels_with_key.clear();
              keys.clear();
            }
1007 1008 1009 1010 1011 1012 1013 1014 1015
          if (!keys.empty())
            keys += ",";
          keys += key;
          if (!channels_with_key.empty())
            channels_with_key += ",";
          channels_with_key += chan;
        }
      else
        {
1016 1017 1018 1019 1020
          if (channels.size() >= 300)
            { // Arbitrary size, to make sure we never send more than 512
              this->send_join_command(channels, {});
              channels.clear();
            }
1021 1022 1023 1024 1025 1026 1027 1028 1029
          if (!channels.empty())
            channels += ",";
          channels += chan;
        }
    }
  if (!channels.empty())
    this->send_join_command(channels, {});
  if (!channels_with_key.empty())
    this->send_join_command(channels_with_key, keys);
louiz’'s avatar
louiz’ committed
1030 1031
  this->channels_to_join.clear();
}
1032 1033 1034

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
1035
  const std::string chan_name = message.arguments[0];
1036
  IrcChannel* channel = this->get_channel(chan_name);
1037 1038
  if (!channel->joined)
    return ;
1039 1040 1041 1042 1043 1044 1045
  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;
1046
      bool self = channel->get_self() && channel->get_self()->nick == nick;
1047
      auto user_ptr = channel->remove_user(user);
1048
      if (self)
louiz’'s avatar
louiz’ committed
1049
      {
louiz’'s avatar
louiz’ committed
1050
        this->channels.erase(utils::tolower(chan_name));
louiz’'s avatar
louiz’ committed
1051 1052 1053
        // channel pointer is now invalid
        channel = nullptr;
      }
1054 1055 1056 1057
      Iid iid;
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.type = Iid::Type::Channel;
1058
      this->bridge.send_muc_leave(iid, *user_ptr, txt, self, true, {}, this);
1059 1060
    }
}
louiz’'s avatar
louiz’ committed
1061

louiz’'s avatar
louiz’ committed
1062 1063 1064 1065
void IrcClient::on_error(const IrcMessage& message)
{
  const std::string leave_message = message.arguments[0];
  // The user is out of all the channels
1066
  for (const auto& pair: this->channels)
louiz’'s avatar
louiz’ committed
1067 1068
  {
    Iid iid;
1069
    iid.set_local(pair.first);
louiz’'s avatar
louiz’ committed
1070
    iid.set_server(this->hostname);
1071
    iid.type = Iid::Type::Channel;
1072
    IrcChannel* channel = pair.second.get();