summaryrefslogtreecommitdiff
path: root/src/irc/irc_client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc/irc_client.cpp')
-rw-r--r--src/irc/irc_client.cpp235
1 files changed, 166 insertions, 69 deletions
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index de6b089..bacb89e 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -1,3 +1,4 @@
+#include <utility>
#include <utils/timed_events.hpp>
#include <database/database.hpp>
#include <irc/irc_message.hpp>
@@ -14,13 +15,13 @@
#include <sstream>
#include <iostream>
#include <stdexcept>
+#include <algorithm>
#include <cstring>
#include <chrono>
#include <string>
#include "biboumi.h"
-#include "louloulibs.h"
using namespace std::string_literals;
using namespace std::chrono_literals;
@@ -61,11 +62,14 @@ static const std::unordered_map<std::string,
{"333", {&IrcClient::on_topic_who_time_received, {4, 0}}},
{"RPL_TOPICWHOTIME", {&IrcClient::on_topic_who_time_received, {4, 0}}},
{"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
+ {"367", {&IrcClient::on_banlist, {3, 0}}},
+ {"368", {&IrcClient::on_banlist_end, {3, 0}}},
{"396", {&IrcClient::on_own_host_received, {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}}},
{"443", {&IrcClient::on_useronchannel, {3, 0}}},
+ {"475", {&IrcClient::on_channel_bad_key, {3, 0}}},
{"ERR_USERONCHANNEL", {&IrcClient::on_useronchannel, {3, 0}}},
{"001", {&IrcClient::on_welcome_message, {1, 0}}},
{"PART", {&IrcClient::on_part, {1, 0}}},
@@ -113,7 +117,6 @@ static const std::unordered_map<std::string,
{"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}}},
@@ -127,16 +130,16 @@ static const std::unordered_map<std::string,
{"502", {&IrcClient::on_generic_error, {2, 0}}},
};
-IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
- const std::string& nickname, const std::string& username,
- const std::string& realname, const std::string& user_hostname,
+IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
+ std::string nickname, std::string username,
+ std::string realname, std::string user_hostname,
Bridge& bridge):
- TCPSocketHandler(poller),
- hostname(hostname),
- user_hostname(user_hostname),
- username(username),
- realname(realname),
- current_nick(nickname),
+ TCPClientSocketHandler(poller),
+ hostname(std::move(hostname)),
+ user_hostname(std::move(user_hostname)),
+ username(std::move(username)),
+ realname(std::move(realname)),
+ current_nick(std::move(nickname)),
bridge(bridge),
welcomed(false),
chanmodes({"", "", "", ""}),
@@ -153,11 +156,11 @@ IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- std::vector<std::string> ports = utils::split(options.ports, ';', false);
+ std::vector<std::string> ports = utils::split(options.col<Database::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);
+ ports = utils::split(options.col<Database::TlsPorts>(), ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, true);
# endif // BOTAN_FOUND
@@ -201,7 +204,7 @@ void IrcClient::start()
# 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);
+ this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
# endif
#endif
this->connect(this->hostname, port, tls);
@@ -272,8 +275,8 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- if (!options.pass.value().empty())
- this->send_pass_command(options.pass.value());
+ if (!options.col<Database::Pass>().empty())
+ this->send_pass_command(options.col<Database::Pass>());
#endif
this->send_nick_command(this->current_nick);
@@ -281,10 +284,10 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
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();
+ if (!options.col<Database::Username>().empty())
+ this->username = options.col<Database::Username>();
+ if (!options.col<Database::Realname>().empty())
+ this->realname = options.col<Database::Realname>();
this->send_user_command(username, realname);
}
else
@@ -343,7 +346,7 @@ void IrcClient::parse_in_buffer(const size_t)
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);
+ this->consume_in_buffer(pos + 2);
log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
// Call the standard callback (if any), associated with the command
@@ -386,10 +389,10 @@ void IrcClient::send_message(IrcMessage&& message)
std::string res;
if (!message.prefix.empty())
res += ":" + std::move(message.prefix) + " ";
- res += std::move(message.command);
+ res += message.command;
for (const std::string& arg: message.arguments)
{
- if (arg.find(" ") != std::string::npos ||
+ if (arg.find(' ') != std::string::npos ||
(!arg.empty() && arg[0] == ':'))
{
res += " :" + arg;
@@ -455,7 +458,12 @@ void IrcClient::send_quit_command(const std::string& reason)
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
{
if (this->welcomed == false)
- this->channels_to_join.emplace_back(chan_name, password);
+ {
+ 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);
+ }
else if (password.empty())
this->send_message(IrcMessage("JOIN", {chan_name}));
else
@@ -501,15 +509,7 @@ void IrcClient::send_private_message(const std::string& username, const std::str
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)
- {
- if (chan_name.empty())
- this->leave_dummy_channel(status_message);
- else
- this->send_message(IrcMessage("PART", {chan_name, status_message}));
- channel->parting = true;
- }
+ this->send_message(IrcMessage("PART", {chan_name, status_message}));
}
void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments)
@@ -546,9 +546,18 @@ void IrcClient::forward_server_message(const IrcMessage& message)
void IrcClient::on_notice(const IrcMessage& message)
{
std::string from = message.prefix;
- const std::string to = message.arguments[0];
+ std::string to = message.arguments[0];
const std::string body = message.arguments[1];
+ // 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);
+ }
+
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 ;
@@ -635,15 +644,18 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message)
std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
for (const std::string& nick: nicks)
{
- const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
- if (user->nick != channel->get_self()->nick)
+ // 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())
{
- this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
+ // We now know our own modes, that’s all.
+ channel->get_self()->modes = tmp_user.modes;
}
else
- {
- // we now know the modes of self, so copy the modes into self
- channel->get_self()->modes = user->modes;
+ { // 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);
}
}
}
@@ -657,13 +669,11 @@ void IrcClient::on_channel_join(const IrcMessage& message)
else
channel = this->get_channel(chan_name);
const std::string nick = message.prefix;
+ IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
if (channel->joined == false)
- channel->set_self(nick);
+ channel->set_self(user);
else
- {
- 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);
- }
+ this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
}
void IrcClient::on_channel_message(const IrcMessage& message)
@@ -776,6 +786,43 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message)
this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
}
+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);
+ }
+}
+
void IrcClient::on_own_host_received(const IrcMessage& message)
{
this->own_host = message.arguments[1];
@@ -799,10 +846,10 @@ 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)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
this->bridge.send_nickname_conflict_error(iid, nickname);
@@ -816,10 +863,10 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
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)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
this->bridge.send_presence_error(iid, nickname,
@@ -847,14 +894,53 @@ void IrcClient::on_welcome_message(const IrcMessage& message)
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- if (!options.afterConnectionCommand.value().empty())
- this->send_raw(options.afterConnectionCommand.value());
+ if (!options.col<Database::AfterConnectionCommand>().empty())
+ this->send_raw(options.col<Database::AfterConnectionCommand>());
#endif
// Install a repeated events to regularly send a PING
TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
"PING"s + this->hostname + this->bridge.get_jid()));
+ std::string channels{};
+ std::string channels_with_key{};
+ std::string keys{};
+
for (const auto& tuple: this->channels_to_join)
- this->send_join_command(std::get<0>(tuple), std::get<1>(tuple));
+ {
+ const auto& chan = std::get<0>(tuple);
+ const auto& key = std::get<1>(tuple);
+ if (chan.empty())
+ continue;
+ if (!key.empty())
+ {
+ 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();
+ }
+ if (!keys.empty())
+ keys += ",";
+ keys += key;
+ if (!channels_with_key.empty())
+ channels_with_key += ",";
+ channels_with_key += chan;
+ }
+ else
+ {
+ if (channels.size() >= 300)
+ { // Arbitrary size, to make sure we never send more than 512
+ this->send_join_command(channels, {});
+ channels.clear();
+ }
+ 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);
this->channels_to_join.clear();
// Indicate that the dummy channel is joined as well, if needed
if (this->dummy_channel.joining)
@@ -883,20 +969,19 @@ void IrcClient::on_part(const IrcMessage& message)
if (user)
{
std::string nick = user->nick;
+ bool self = channel->get_self() && channel->get_self()->nick == nick;
channel->remove_user(user);
Iid iid;
iid.set_local(chan_name);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- bool self = channel->get_self()->nick == nick;
if (self)
{
- channel->joined = false;
this->channels.erase(utils::tolower(chan_name));
// channel pointer is now invalid
channel = nullptr;
}
- this->bridge.send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self);
+ this->bridge.send_muc_leave(iid, std::move(nick), txt, self);
}
}
@@ -904,17 +989,17 @@ 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)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- IrcChannel* channel = it->second.get();
+ IrcChannel* channel = pair.second.get();
if (!channel->joined)
continue;
std::string own_nick = channel->get_self()->nick;
- this->bridge.send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
+ this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true);
}
this->channels.clear();
this->send_gateway_message("ERROR: "s + leave_message);
@@ -925,10 +1010,10 @@ 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)
+ for (const auto& pair: this->channels)
{
- const std::string chan_name = it->first;
- IrcChannel* channel = it->second.get();
+ const std::string& chan_name = pair.first;
+ IrcChannel* channel = pair.second.get();
const IrcUser* user = channel->find_user(message.prefix);
if (user)
{
@@ -938,7 +1023,7 @@ void IrcClient::on_quit(const IrcMessage& message)
iid.set_local(chan_name);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false);
+ this->bridge.send_muc_leave(iid, std::move(nick), txt, false);
}
}
}
@@ -974,9 +1059,9 @@ void IrcClient::on_nick(const IrcMessage& message)
{
change_nick_func("", &this->get_dummy_channel());
}
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
- change_nick_func(it->first, it->second.get());
+ change_nick_func(pair.first, pair.second.get());
}
}
@@ -1019,6 +1104,18 @@ void IrcClient::on_mode(const IrcMessage& message)
this->on_user_mode(message);
}
+void IrcClient::on_channel_bad_key(const IrcMessage& message)
+{
+ this->on_generic_error(message);
+ const std::string& nickname = message.arguments[0];
+ const std::string& channel = message.arguments[1];
+ std::string text;
+ if (message.arguments.size() > 2)
+ text = message.arguments[2];
+
+ this->bridge.send_presence_error({channel, this->hostname, Iid::Type::Channel}, nickname, "auth", "not-authorized", "", text);
+}
+
void IrcClient::on_channel_mode(const IrcMessage& message)
{
// For now, just transmit the modes so the user can know what happens
@@ -1075,7 +1172,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
{
// That mode can also be of type B if it is present in the
// prefix_to_mode map
- for (const std::pair<char, char>& pair: this->prefix_to_mode)
+ for (const auto& pair: this->prefix_to_mode)
if (pair.second == c)
{
type = 1;
@@ -1148,14 +1245,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel()
return this->dummy_channel;
}
-void IrcClient::leave_dummy_channel(const std::string& exit_message)
+void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource)
{
if (!this->dummy_channel.joined)
return;
this->dummy_channel.joined = false;
this->dummy_channel.joining = false;
this->dummy_channel.remove_all_users();
- this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true);
+ this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, resource);
}
#ifdef BOTAN_FOUND
@@ -1163,7 +1260,7 @@ bool IrcClient::abort_on_invalid_cert() const
{
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname);
- return options.verifyCert.value();
+ return options.col<Database::VerifyCert>();
#endif
return true;
}