#include "instance.hpp" #include "config.hpp" #include "uid.hpp" #include #include #include #include #include #include #include using fmt::operator""_format; inline std::string_view operator ""_sv(const char *str, unsigned long len) { return {str, static_cast(len)}; } static std::vector strSplit(std::string_view s, char delimiter, size_t times = 0) { std::vector to_return; decltype(s.size()) start = 0, finish = 0; while ((finish = s.find_first_of(delimiter, start)) != std::string_view::npos) { to_return.emplace_back(s.substr(start, finish - start)); start = finish + 1; if (to_return.size() == times) { break; } } to_return.emplace_back(s.substr(start)); return to_return; } static std::tuple colonSplit(std::string_view s) { // Find the colon auto colonPos = s.find(" :"); if (colonPos == s.npos) { return {s, ""}; } // Split there return {s.substr(0, colonPos), s.substr(colonPos+2, s.size()-1)}; } void Event::parse(std::string_view str) { auto split = strSplit(str, ' ', 2); // Check split size if (split.size() < 2) { throw ParseError("In event parser: Events are expected to have both a sender and name"); } // Move values split[0] = {split[0].data()+1, split[0].size()-1}; // Erase leading ':' auto id_len = split[0].size(); if (id_len == SUID_len) { sender = SUID(std::move(split[0])); } else if (id_len == UUID_len) { sender = UUID(std::move(split[0])); } else { sender = std::string(split[0]); } name = std::move(split[1]); if (split.size() == 3) { // Get args and text std::tie(args, text) = colonSplit(split[2]); } } void Command::parse(std::string_view str) { auto split = strSplit(str, ' ', 1); name = std::move(split[0]); // Get text from args std::tie(args, text) = colonSplit(split[1]); } template void ModeSet::parse(std::string_view in, NetworkInfo &netInfo) { enum { ADDING, REMOVING } op; // Split up auto split = strSplit(in, ' '); auto paramIt = split.begin() + 1; // Iterate through mode characters for (const char character : split[0]) { if (character == '+') { op = ADDING; } else if (character == '-') { op = REMOVING; } else { if (op == ADDING) { if (str.find(character) == str.npos) { str.push_back(character); if constexpr(channelModes) { if (netInfo.channelModes.takesParamOnSet(character)) { if (paramIt == split.end()) { throw DesyncError(); } params.push_back({character, std::string(*(paramIt++))}); } } } else { throw DesyncError(); } } else if (op == REMOVING) { auto res = str.find(character); if (res != str.npos) { str.erase(res); if constexpr(channelModes) { if (netInfo.channelModes.takesParamOnUnset(character)) { if (paramIt == split.end()) { throw DesyncError(); } for (auto it = params.begin(); ; it++) { if (it == params.end()) { throw DesyncError(); } auto &[c, d] = *it; if (c == character && (d == *paramIt || *paramIt == "*")) { params.erase(it); break; } } paramIt++; } } } else { throw DesyncError(); } } } } } void User::parse_euid(const Event& event, NetworkInfo& netInfo) { this->server = std::get(event.sender.id); auto split = strSplit(event.args, ' ', 8); // Check size if (split.size() != 9) { throw ParseError("In euid parser: This euid event does not have enough arguments"); } // Move values nick = std::move(split[0]); hops = std::stoull(std::string(split[1])); umode.parse(split[3], netInfo); ident = std::move(split[4]); host = std::move(split[5]); realhost = std::move(split[6]); uid = UUID(std::move(split[7])); // Get realname realname = std::move(event.text); } void User::removeChannel(const u_Channel& channel) { std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj.get() == channel;}); } void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo) { this->server = std::get(event.sender.id); auto split = strSplit(event.args, ' ', 2); // Check size if (split.size() != 3) { throw ParseError("In euid parser: This euid event does not have enough arguments"); } // Move values name = std::move(split[1]); mode.parse(split[2], netInfo); // Get members for (auto& raw_uuid : strSplit(event.text, ' ')) { // Erase leading sign if (raw_uuid.size() > UUID_len) { raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1}; } // Find user in cache auto res = cache.find_user_by_uid(UUID(raw_uuid)); if (res == cache.users.end()) { throw DesyncError(); } u_User& member = *res; // Append member to list members.push_back(member); } } void Channel::removeMember(const u_User& member) { std::remove_if(members.begin(), members.end(), [&] (auto obj) {return obj.get() == member;}); } std::vector::iterator Cache::find_user_by_nick(std::string_view nick) { for (auto it = users.begin(); ; it++) { if (it == users.end() || it->get()->nick == nick) { return it; } } } std::vector::iterator Cache::find_user_by_uid(const UUID& uid) { for (auto it = users.begin(); ; it++) { if (it == users.end() || it->get()->uid == uid) { return it; } } } std::vector::iterator Cache::find_channel_by_name(std::string_view name) { for (auto it = channels.begin(); ; it++) { if (it == channels.end() || it->get()->name == name) { return it; } } } async::result Instance::run() { // Create connection socket.reset(new uvpp::tcp{s}); co_await socket->connect(addr); socket->recv_start(); // Login co_await login(); // Mainloop while (true) { // Read auto data = co_await socket->recv(); // Check for general error if (data->error()) { continue; } // Check for broken connection if (data->broken()) { break; } // Make string auto dataStr = std::string_view{ data->data.get(), static_cast(data->nread) }; // Split by newlines for (auto& line : strSplit(dataStr, '\n')) { if (line.size() < 2) { continue; // Empty line } // Remove \r if (line.back() == '\r') { line = {line.data(), line.size()-1}; } // Check if server sent an event or command, then parse and process it if (line[0] == ':') { Event event; event.parse(std::move(line)); async::detach(process(std::move(event))); } else { Command command; command.parse(std::move(line)); async::detach(process(std::move(command))); } } } } async::result Instance::login() { co_await socket->send("PASS {} TS 6 :{}\n"_format(config.get().auth.send_password, config.get().server.uid.str())); co_await socket->send("CAPAB :QS EX IE KLN UNKLN ENCAP TB SERVICES EUID EOPMOD MLOCK\n"_sv); co_await socket->send("SERVER {} 1 :{}{}\n"_format(config.get().server.name, config.get().server.hidden ? "(H) " : "", config.get().server.description)); co_await socket->send("SVINFO 6 3 0 :{}\n"_format(time(nullptr))); } async::result Instance::burst() { // End burst co_await send_ping(); client_bursting = false; // Wait for burst to complete std::cout << "I'm done bursting too. Waiting for network informations..." << std::endl; co_await socket->send("VERSION\n"_sv); co_await netInfo.wait_ready(); std::cout << "Ready." << std::endl; } async::result Instance::process(const Command command) { if (command.name == "PASS") { // Check password { auto given_password = strSplit(command.args, ' ', 1)[0]; if (given_password != config.get().auth.accept_password) { throw ConnectionError("Server supplied wrong password during authentication"); } authed = true; } // Get server ID connected_server.uid = SUID(command.text); } if (!authed) { throw ConnectionError("Server tried to execute a command before authenticating"); } else if (command.name == "SERVER") { // Get name and description of connected server connected_server.name = strSplit(command.args, ' ', 1)[0]; connected_server.description = command.text; } else if (command.name == "PING") { // End of burst if (server_bursting) { server_bursting = false; std::cout << "Server burst is over. It's my turn." << std::endl; co_await burst(); } // Reply co_await socket->send(":{} PONG {} {} :{}\n"_format(config.get().server.uid.str(), config.get().server.name, command.args, command.text)); } } async::result Instance::process(const Event event) { if (event.name == "NOTICE") { // Don't do anything special } else if (!authed) { throw ConnectionError("Server tried to send an event before authenticating"); } // Fetched info else if (event.name == "005") { // Check if that 005 was for me if (event.args.find(config.get().server.uid.str()) != 0) { co_return; } // Split the list auto split = strSplit(event.args, ' '); // Iterate and find the information we need for (const auto& field : split) { // Split into key and value auto split = strSplit(field, '=', 1); // Check size if (split.size() != 2) { continue; } // Check if we've got the right key if (split[0] == "NETWORK") { netInfo.name = std::move(split[1]); netInfo.fields_received++; } else if (split[0] == "CHANMODES") { auto modeLists = strSplit(split[1], ',', 3); netInfo.channelModes = { .listModes = std::string(modeLists[0]), .paramOnSetAndUnsetModes = std::string(modeLists[1]), .paramOnSetOnlyModes = std::string(modeLists[2]), .paramLessModes = std::string(modeLists[3]) }; netInfo.fields_received++; } } // Check if everything needed has been fetched if (netInfo.fields_received == 2 && !netInfo.ready) { netInfo.mark_ready(); } } else { co_await netInfo.wait_ready(); std::clog << event.dump() << std::flush; // User updates if (event.name == "EUID") { // User connected to the network // Create user and parse event auto user = std::make_unique(); user->parse_euid(event, netInfo); // Append user to cache cache.users.push_back(std::move(user)); } else if (event.name == "QUIT") { // User disconnected from the network // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Delete user from all channels for (auto& channel : res->get()->channels) { channel.get()->removeMember({*res}); } // Delete user from cache cache.users.erase(res); } else if (event.name == "NICK") { // User changed their nick // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Set nick res->get()->nick = strSplit(event.args, ' ', 1)[0]; } else if (event.name == "MODE") { // User changed their mode // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Update mode res->get()->umode.parse(event.text, netInfo); } // Channel updates else if (event.name == "SJOIN") { // Channel was created // Create channel and parse event auto channel = std::make_unique(); channel->parse_sjoin(event, cache, netInfo); // Append channel to cache cache.channels.push_back(std::move(channel)); } else if (event.name == "TOPIC" || event.name == "TB") { // Channels topic changed // Find channel in cache auto res = cache.find_channel_by_name(strSplit(event.args, ' ', 1)[0]); if (res == cache.channels.end()) { throw DesyncError(); } // Set topic res->get()->topic = event.text; } else if (event.name == "JOIN") { // User joined existing channel // Split args auto split = strSplit(event.args, ' ', 2); if (split.size() != 3) { throw ParseError("In join event parser: join even did not receive enough arguments (expected 3)"); } // Get channel from cache auto c_res = cache.find_channel_by_name(split[1]); if (c_res == cache.channels.end()) { throw DesyncError(); } // Get user from cache auto u_res = cache.find_user_by_uid(std::get(event.sender.id)); if (u_res == cache.users.end()) { throw DesyncError(); } // Assign user to channel and vice versa c_res->get()->members.push_back(*u_res); u_res->get()->channels.push_back(*c_res); // Update channel modes c_res->get()->mode.parse(split[2], netInfo); } else if (event.name == "PART" || event.name == "KICK") { // User left channel // Get channel from cache auto c_res = cache.find_channel_by_name(strSplit(event.args, ' ', 1)[0]); if (c_res == cache.channels.end()) { throw DesyncError(); } // Get user from cache auto u_res = cache.find_user_by_uid(std::get(event.sender.id)); if (u_res == cache.users.end()) { throw DesyncError(); } // Remove channel from both user and channel c_res->get()->removeMember(*u_res); u_res->get()->removeChannel(*c_res); } else if (event.name == "TMODE" || event.name == "BMASK") { // Channel modes changed // Split args std::string_view modes, channelName; { auto split = strSplit(event.args, ' ', 2); if (split.size() != 3) { throw DesyncError(); } channelName = split[1]; modes = split[2]; } // Get channel from cache auto c_res = cache.find_channel_by_name(channelName); if (c_res == cache.channels.end()) { throw DesyncError(); } // Apply changes c_res->get()->mode.parse("+{} {}"_format(modes, event.text), netInfo); } } //std::clog << event.dump() << std::flush; } async::result Instance::send_ping() { co_await socket->send("PING :{}\n"_format(connected_server.uid.str())); }