diff options
Diffstat (limited to 'src/irc/irc_client.cpp')
-rw-r--r-- | src/irc/irc_client.cpp | 235 |
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; } |