Compare commits

..

No commits in common. "44ef4c0689e0dada81cd1a0b0ff7bc8621056ca1" and "5f2439e5af03760bdebe4f2db62d9ab2c721e485" have entirely different histories.

20 changed files with 493 additions and 598 deletions

View File

@ -61,8 +61,8 @@ static auto start_irc(
// Configure CHALLENGE on registration if applicable // Configure CHALLENGE on registration if applicable
if (not settings.challenge_username.empty() && not settings.challenge_key_file.empty()) { if (not settings.challenge_username.empty() && not settings.challenge_key_file.empty()) {
if (auto key = myirc::key_from_file(settings.challenge_key_file, settings.challenge_key_password)) { if (auto key = myirc::key_from_file(settings.challenge_key_file, settings.challenge_key_password)) {
client->sig_registered.connect([&settings, client, key = std::move(key)]() { client->sig_registered.connect([&settings, connection, key = std::move(key)]() {
Challenge::start(client, settings.challenge_username, key); Challenge::start(connection, settings.challenge_username, key);
}); });
} }
} }
@ -74,7 +74,7 @@ static auto start_irc(
// On disconnect reconnect in 5 seconds // On disconnect reconnect in 5 seconds
// connection is captured in the disconnect handler so it can keep itself alive // connection is captured in the disconnect handler so it can keep itself alive
connection->sig_disconnect.connect( connection->sig_disconnect.connect(
[&io, &settings, connection, webhook](auto) { [&io, &settings, connection, webhook]() {
webhook->clear_client(); webhook->clear_client();
auto timer = std::make_shared<boost::asio::steady_timer>(io); auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(5s); timer->expires_after(5s);

View File

@ -64,7 +64,7 @@ auto compute_signature(const std::string_view secret, const std::string_view bod
unsigned int digest_length = EVP_MAX_MD_SIZE; unsigned int digest_length = EVP_MAX_MD_SIZE;
unsigned char digest[EVP_MAX_MD_SIZE]; unsigned char digest[EVP_MAX_MD_SIZE];
HMAC(EVP_sha256(), secret.data(), static_cast<int>(secret.size()), reinterpret_cast<const unsigned char *>(body.data()), body.size(), digest, &digest_length); HMAC(EVP_sha256(), secret.data(), secret.size(), reinterpret_cast<const unsigned char *>(body.data()), body.size(), digest, &digest_length);
std::stringstream ss; std::stringstream ss;
ss << "sha256="; ss << "sha256=";
@ -415,7 +415,7 @@ auto Webhooks::send_notice(std::string_view target, std::string message) -> void
{ {
if (client_) if (client_)
{ {
client_->send_notice(target, message); client_->get_connection().send_notice(target, message);
} }
else else
{ {
@ -428,7 +428,7 @@ auto Webhooks::set_client(std::shared_ptr<myirc::Client> client) -> void
client_ = std::move(client); client_ = std::move(client);
for (auto &&[target, message] : std::move(events_)) for (auto &&[target, message] : std::move(events_))
{ {
client_->send_notice(target, message); client_->get_connection().send_notice(target, message);
} }
events_.clear(); events_.clear();
} }

View File

@ -9,7 +9,8 @@
#include <cstddef> #include <cstddef>
#include <string_view> #include <string_view>
namespace mybase64 { namespace mybase64
{
inline constexpr auto encoded_size(std::size_t len) -> std::size_t inline constexpr auto encoded_size(std::size_t len) -> std::size_t
{ {
@ -34,8 +35,10 @@ auto encode(std::string_view input, char* output) -> void;
* *
* @param input Base64 input text * @param input Base64 input text
* @param output Target buffer for decoded value * @param output Target buffer for decoded value
* @return pointer to end of output on success * @param outlen Output parameter for decoded length
* @return true success
* @return false failure
*/ */
auto decode(std::string_view input, char* output) -> char*; auto decode(std::string_view input, char* output, std::size_t* outlen) -> bool;
} // namespace } // namespace

View File

@ -1,102 +1,113 @@
#include "mybase64.hpp" #include "mybase64.hpp"
#include <array>
#include <climits> #include <climits>
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
namespace mybase64 { namespace mybase64
{
namespace {
constexpr std::array<char, 64> alphabet{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
constexpr std::array<std::int8_t, 256> alphabet_values = []() constexpr {
std::array<std::int8_t, 256> result;
result.fill(-1);
std::int8_t v = 0;
for (auto const k : alphabet)
{
result[k] = v++;
}
return result;
}();
}
static_assert(CHAR_BIT == 8); static_assert(CHAR_BIT == 8);
auto encode(std::string_view const input, char* output) -> void auto encode(std::string_view const input, char* output) -> void
{ {
auto cursor = std::begin(input); static char const* const alphabet =
auto const end = std::end(input); "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
while (end - cursor >= 3) auto cursor = std::begin(input);
{ auto const end = std::end(input);
std::uint32_t buffer = std::uint8_t(*cursor++);
buffer <<= 8;
buffer |= std::uint8_t(*cursor++);
buffer <<= 8;
buffer |= std::uint8_t(*cursor++);
*output++ = alphabet[(buffer >> 6 * 3) % 64]; while (end - cursor >= 3)
*output++ = alphabet[(buffer >> 6 * 2) % 64]; {
*output++ = alphabet[(buffer >> 6 * 1) % 64]; uint32_t buffer = uint8_t(*cursor++);
*output++ = alphabet[(buffer >> 6 * 0) % 64]; buffer <<= 8; buffer |= uint8_t(*cursor++);
} buffer <<= 8; buffer |= uint8_t(*cursor++);
if (cursor < end) *output++ = alphabet[(buffer >> 6 * 3) % 64];
{ *output++ = alphabet[(buffer >> 6 * 2) % 64];
std::uint32_t buffer = std::uint8_t(*cursor++) << 10; *output++ = alphabet[(buffer >> 6 * 1) % 64];
if (cursor < end) *output++ = alphabet[(buffer >> 6 * 0) % 64];
buffer |= std::uint8_t(*cursor) << 2; }
*output++ = alphabet[(buffer >> 12) % 64]; if (cursor < end)
*output++ = alphabet[(buffer >> 6) % 64]; {
*output++ = cursor < end ? alphabet[(buffer % 64)] : '='; uint32_t buffer = uint8_t(*cursor++) << 10;
*output++ = '='; if (cursor < end) buffer |= uint8_t(*cursor) << 2;
}
*output = '\0'; *output++ = alphabet[(buffer >> 12) % 64];
*output++ = alphabet[(buffer >> 6) % 64];
*output++ = cursor < end ? alphabet[(buffer % 64)] : '=';
*output++ = '=';
}
*output = '\0';
} }
auto decode(std::string_view const input, char* output) -> char* auto decode(std::string_view const input, char* const output, std::size_t* const outlen) -> bool
{ {
std::uint32_t buffer = 1; static int8_t const alphabet_values[] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 0x3e, -1, -1, -1, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, -1, -1, -1, -1, -1, -1,
-1, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, -1, -1, -1, -1, -1,
-1, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
};
for (auto const c : input) uint32_t buffer = 1;
{ char* cursor = output;
if (auto const value = alphabet_values[uint8_t(c)]; -1 != value)
{ for (char c : input) {
buffer = (buffer << 6) | value; int8_t const value = alphabet_values[uint8_t(c)];
if (buffer & 1 << 6 * 4) if (-1 == value) continue;
{
*output++ = buffer >> 16; buffer = (buffer << 6) | value;
*output++ = buffer >> 8;
*output++ = buffer >> 0; if (buffer & 1<<6*4) {
buffer = 1; *cursor++ = buffer >> 8*2;
} *cursor++ = buffer >> 8*1;
*cursor++ = buffer >> 8*0;
buffer = 1;
} }
} }
if (buffer & 1 << 6 * 3) if (buffer & 1<<6*3) {
{ *cursor++ = buffer >> 10;
*output++ = buffer >> 10; *cursor++ = buffer >> 2;
*output++ = buffer >> 2; } else if (buffer & 1<<6*2) {
*cursor++ = buffer >> 4;
} else if (buffer & 1<<6*1) {
return false;
} }
else if (buffer & 1 << 6 * 2) *outlen = cursor - output;
{ return true;
*output++ = buffer >> 4;
}
else if (buffer & 1 << 6 * 1)
{
return nullptr;
}
return output;
} }
} // namespace } // namespace

View File

@ -1,7 +1,7 @@
add_custom_command( add_custom_command(
OUTPUT irc_commands.inc OUTPUT irc_commands.inc
COMMAND COMMAND
/opt/homebrew/bin/gperf gperf
-C -Z IrcCommandHash -K text -L C++ -t -C -Z IrcCommandHash -K text -L C++ -t
--output-file irc_commands.inc --output-file irc_commands.inc
${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf ${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf
@ -20,7 +20,6 @@ add_library(myirc STATIC
ratelimit.cpp ratelimit.cpp
sasl_mechanism.cpp sasl_mechanism.cpp
snote.cpp snote.cpp
linebuffer.cpp
) )
target_include_directories(myirc PUBLIC include) target_include_directories(myirc PUBLIC include)

View File

@ -9,7 +9,7 @@ auto Bot::start(std::shared_ptr<Client> self) -> std::shared_ptr<Bot>
const auto thread = std::make_shared<Bot>(std::move(self)); const auto thread = std::make_shared<Bot>(std::move(self));
thread->self_->sig_chat.connect([thread](auto &chat, bool) { thread->self_->sig_chat.connect([thread](auto &chat) {
thread->on_chat(chat); thread->on_chat(chat);
}); });

View File

@ -14,9 +14,9 @@
namespace myirc { namespace myirc {
Challenge::Challenge(Ref<EVP_PKEY> key, std::shared_ptr<Client> client) Challenge::Challenge(Ref<EVP_PKEY> key, std::shared_ptr<Connection> connection)
: key_{std::move(key)} : key_{std::move(key)}
, client_{std::move(client)} , connection_{std::move(connection)}
{} {}
auto Challenge::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void { auto Challenge::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void {
@ -31,6 +31,7 @@ auto Challenge::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void {
break; break;
case IrcCommand::RPL_YOUREOPER: case IrcCommand::RPL_YOUREOPER:
slot_.disconnect(); slot_.disconnect();
//connection_->send_ping("mitigation");
break; break;
case IrcCommand::RPL_ENDOFRSACHALLENGE2: case IrcCommand::RPL_ENDOFRSACHALLENGE2:
finish_challenge(); finish_challenge();
@ -45,10 +46,9 @@ auto Challenge::finish_challenge() -> void
size_t len = mybase64::decoded_size(buffer_.size()); size_t len = mybase64::decoded_size(buffer_.size());
std::vector<unsigned char> ciphertext(len, 0); std::vector<unsigned char> ciphertext(len, 0);
auto decode_end = mybase64::decode(buffer_, reinterpret_cast<char*>(ciphertext.data())); if (not mybase64::decode(buffer_, reinterpret_cast<char*>(ciphertext.data()), &len))
if (decode_end == nullptr )
return log_openssl_errors("Challenge base64::decode: "); return log_openssl_errors("Challenge base64::decode: ");
ciphertext.resize(decode_end - reinterpret_cast<char*>(ciphertext.data())); ciphertext.resize(len);
// Setup decryption context // Setup decryption context
Ref<EVP_PKEY_CTX> ctx{EVP_PKEY_CTX_new(key_.get(), nullptr)}; Ref<EVP_PKEY_CTX> ctx{EVP_PKEY_CTX_new(key_.get(), nullptr)};
@ -80,15 +80,15 @@ auto Challenge::finish_challenge() -> void
buffer_[0] = '+'; buffer_[0] = '+';
mybase64::encode(std::string_view{(char*)digest, digestlen}, buffer_.data() + 1); mybase64::encode(std::string_view{(char*)digest, digestlen}, buffer_.data() + 1);
client_->send_challenge(buffer_); connection_->send_challenge(buffer_);
buffer_.clear(); buffer_.clear();
} }
auto Challenge::start(std::shared_ptr<Client> client, const std::string_view user, Ref<EVP_PKEY> ref) -> std::shared_ptr<Challenge> auto Challenge::start(std::shared_ptr<Connection> connection, const std::string_view user, Ref<EVP_PKEY> ref) -> std::shared_ptr<Challenge>
{ {
auto self = std::make_shared<Challenge>(std::move(ref), client); auto self = std::make_shared<Challenge>(std::move(ref), connection);
self->slot_ = client->get_connection().sig_ircmsg.connect([self](auto cmd, auto &msg, bool) { self->on_ircmsg(cmd, msg); }); self->slot_ = connection->sig_ircmsg.connect([self](auto cmd, auto &msg) { self->on_ircmsg(cmd, msg); });
client->send_challenge(user); connection->send_challenge(user);
return self; return self;
} }

View File

@ -2,7 +2,6 @@
#include "myirc/connection.hpp" #include "myirc/connection.hpp"
#include "myirc/sasl_mechanism.hpp" #include "myirc/sasl_mechanism.hpp"
#include "myirc/snote.hpp"
#include <mybase64.hpp> #include <mybase64.hpp>
@ -122,7 +121,7 @@ auto Client::on_isupport(const IrcMsg &msg) -> void
} }
} }
auto Client::on_chat(bool notice, const IrcMsg &irc, bool flush) -> void auto Client::on_chat(bool notice, const IrcMsg &irc) -> void
{ {
char status_msg = '\0'; char status_msg = '\0';
std::string_view target = irc.args[0]; std::string_view target = irc.args[0];
@ -138,26 +137,21 @@ auto Client::on_chat(bool notice, const IrcMsg &irc, bool flush) -> void
.source = irc.source, .source = irc.source,
.target = irc.args[0], .target = irc.args[0],
.message = irc.args[1], .message = irc.args[1],
}, flush); });
} }
auto Client::start(std::shared_ptr<Connection> connection) -> std::shared_ptr<Client> auto Client::start(std::shared_ptr<Connection> connection) -> std::shared_ptr<Client>
{ {
auto thread = std::make_shared<Client>(connection); auto thread = std::make_shared<Client>(connection);
connection->sig_ircmsg.connect([thread](auto cmd, auto &msg, bool flush) { connection->sig_ircmsg.connect([thread](auto cmd, auto &msg) {
switch (cmd) switch (cmd)
{ {
case IrcCommand::PRIVMSG: case IrcCommand::PRIVMSG:
thread->on_chat(false, msg, flush); thread->on_chat(false, msg);
break; break;
case IrcCommand::NOTICE: case IrcCommand::NOTICE:
if (auto match = snoteCore.match(msg)) thread->on_chat(true, msg);
{
thread->sig_snote(*match, flush);
} else {
thread->on_chat(true, msg, flush);
}
break; break;
case IrcCommand::JOIN: case IrcCommand::JOIN:
thread->on_join(msg); thread->on_join(msg);
@ -190,19 +184,13 @@ auto Client::start(std::shared_ptr<Connection> connection) -> std::shared_ptr<Cl
case IrcCommand::CAP: case IrcCommand::CAP:
thread->on_cap(msg); thread->on_cap(msg);
break; break;
case IrcCommand::AUTHENTICATE:
thread->on_authenticate_chunk(msg.args[0]);
break;
default: default:
break; break;
} }
}); });
connection->sig_disconnect.connect([thread](auto) { connection->sig_authenticate.connect([thread](auto msg) {
thread->sig_registered.disconnect_all_slots(); thread->on_authenticate(msg);
thread->sig_cap_ls.disconnect_all_slots();
thread->sig_chat.disconnect_all_slots();
thread->sig_snote.disconnect_all_slots();
}); });
return thread; return thread;
@ -254,20 +242,20 @@ auto Client::on_authenticate(const std::string_view body) -> void
if (not sasl_mechanism_) if (not sasl_mechanism_)
{ {
BOOST_LOG_TRIVIAL(warning) << "Unexpected AUTHENTICATE from server"sv; BOOST_LOG_TRIVIAL(warning) << "Unexpected AUTHENTICATE from server"sv;
send_authenticate_abort(); connection_->send_authenticate_abort();
return; return;
} }
std::visit( std::visit(
overloaded{ overloaded{
[this](const std::string &reply) { [this](const std::string &reply) {
send_authenticate_encoded(reply); connection_->send_authenticate_encoded(reply);
}, },
[this](SaslMechanism::NoReply) { [this](SaslMechanism::NoReply) {
send_authenticate("*"sv); connection_->send_authenticate("*"sv);
}, },
[this](SaslMechanism::Failure) { [this](SaslMechanism::Failure) {
send_authenticate_abort(); connection_->send_authenticate_abort();
}, },
}, },
sasl_mechanism_->step(body)); sasl_mechanism_->step(body));
@ -282,11 +270,11 @@ auto Client::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void
{ {
if (sasl_mechanism_) if (sasl_mechanism_)
{ {
send_authenticate("*"sv); // abort SASL connection_->send_authenticate("*"sv); // abort SASL
} }
sasl_mechanism_ = std::move(mechanism); sasl_mechanism_ = std::move(mechanism);
send_authenticate(sasl_mechanism_->mechanism_name()); connection_->send_authenticate(sasl_mechanism_->mechanism_name());
} }
static auto tolower_rfc1459(int c) -> int static auto tolower_rfc1459(int c) -> int
@ -346,7 +334,7 @@ auto Client::casemap_compare(std::string_view lhs, std::string_view rhs) const -
auto Client::list_caps() -> void auto Client::list_caps() -> void
{ {
caps_available_.clear(); caps_available_.clear();
send_cap_ls(); connection_->send_cap_ls();
} }
auto Client::on_cap(const IrcMsg &msg) -> void auto Client::on_cap(const IrcMsg &msg) -> void
@ -441,206 +429,4 @@ auto Client::on_cap(const IrcMsg &msg) -> void
} }
} }
auto Client::on_authenticate_chunk(const std::string_view chunk) -> void
{
if (chunk != "+"sv)
{
authenticate_buffer_ += chunk;
}
if (chunk.size() != 400)
{
std::string decoded;
decoded.resize(mybase64::decoded_size(authenticate_buffer_.size()));
std::size_t len;
if (auto decode_end = mybase64::decode(authenticate_buffer_, decoded.data()))
{
decoded.resize(decode_end - decoded.data());
on_authenticate(decoded);
}
else
{
BOOST_LOG_TRIVIAL(debug) << "Invalid AUTHENTICATE base64"sv;
send_authenticate("*"sv); // abort SASL
}
authenticate_buffer_.clear();
}
else if (authenticate_buffer_.size() > 1024)
{
BOOST_LOG_TRIVIAL(debug) << "AUTHENTICATE buffer overflow"sv;
authenticate_buffer_.clear();
send_authenticate("*"sv); // abort SASL
}
}
auto Client::send_ping(std::string_view txt) -> void
{
connection_->write_irc("PING", txt);
}
auto Client::send_pong(std::string_view txt) -> void
{
connection_->write_irc("PONG", txt);
}
auto Client::send_pass(std::string_view password) -> void
{
connection_->write_irc("PASS", password);
}
auto Client::send_user(std::string_view user, std::string_view real) -> void
{
connection_->write_irc("USER", user, "*", "*", real);
}
auto Client::send_nick(std::string_view nick) -> void
{
connection_->write_irc("NICK", nick);
}
auto Client::send_cap_ls() -> void
{
connection_->write_irc("CAP", "LS", "302");
}
auto Client::send_cap_end() -> void
{
connection_->write_irc("CAP", "END");
}
auto Client::send_cap_req(std::string_view caps) -> void
{
connection_->write_irc("CAP", "REQ", caps);
}
auto Client::send_privmsg(std::string_view target, std::string_view message) -> void
{
connection_->write_irc("PRIVMSG", target, message);
}
auto Client::send_notice(std::string_view target, std::string_view message) -> void
{
connection_->write_irc("NOTICE", target, message);
}
auto Client::send_wallops(std::string_view message) -> void
{
connection_->write_irc("WALLOPS", message);
}
auto Client::send_names(std::string_view channel) -> void
{
connection_->write_irc("NAMES", channel);
}
auto Client::send_map() -> void
{
connection_->write_irc("MAP");
}
auto Client::send_get_topic(std::string_view channel) -> void
{
connection_->write_irc("TOPIC", channel);
}
auto Client::send_set_topic(std::string_view channel, std::string_view message) -> void
{
connection_->write_irc("TOPIC", channel, message);
}
auto Client::send_testline(std::string_view target) -> void
{
connection_->write_irc("TESTLINE", target);
}
auto Client::send_masktrace_gecos(std::string_view target, std::string_view gecos) -> void
{
connection_->write_irc("MASKTRACE", target, gecos);
}
auto Client::send_masktrace(std::string_view target) -> void
{
connection_->write_irc("MASKTRACE", target);
}
auto Client::send_testmask_gecos(std::string_view target, std::string_view gecos) -> void
{
connection_->write_irc("TESTMASK", target, gecos);
}
auto Client::send_testmask(std::string_view target) -> void
{
connection_->write_irc("TESTMASK", target);
}
auto Client::send_authenticate(std::string_view message) -> void
{
connection_->write_irc("AUTHENTICATE", message);
}
auto Client::send_join(std::string_view channel) -> void
{
connection_->write_irc("JOIN", channel);
}
auto Client::send_challenge(std::string_view message) -> void
{
connection_->write_irc("CHALLENGE", message);
}
auto Client::send_oper(std::string_view user, std::string_view pass) -> void
{
connection_->write_irc("OPER", user, pass);
}
auto Client::send_kick(std::string_view channel, std::string_view nick, std::string_view reason) -> void
{
connection_->write_irc("KICK", channel, nick, reason);
}
auto Client::send_kill(std::string_view nick, std::string_view reason) -> void
{
connection_->write_irc("KILL", nick, reason);
}
auto Client::send_quit(std::string_view message) -> void
{
connection_->write_irc("QUIT", message);
}
auto Client::send_whois(std::string_view arg1) -> void
{
connection_->write_irc("WHOIS", arg1);
}
auto Client::send_whois_remote(std::string_view arg1, std::string_view arg2) -> void
{
connection_->write_irc("WHOIS", arg1, arg2);
}
auto Client::send_authenticate_abort() -> void
{
send_authenticate("*");
}
auto Client::send_authenticate_encoded(std::string_view body) -> void
{
std::string encoded(mybase64::encoded_size(body.size()), 0);
mybase64::encode(body, encoded.data());
for (size_t lo = 0; lo < encoded.size(); lo += 400)
{
const auto hi = std::min(lo + 400, encoded.size());
const std::string_view chunk{encoded.begin() + lo, encoded.begin() + hi};
send_authenticate(chunk);
}
if (encoded.size() % 400 == 0)
{
send_authenticate("+"sv);
}
}
} // namespace myirc } // namespace myirc

View File

@ -1,17 +1,12 @@
#include "myirc/connection.hpp" #include "myirc/connection.hpp"
#include "myirc/linebuffer.hpp" #include "myirc/linebuffer.hpp"
#include <openssl/asn1.h> #include "myirc/snote.hpp"
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <socks5.hpp>
#include <mybase64.hpp> #include <mybase64.hpp>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <sstream>
#include <iomanip>
namespace myirc { namespace myirc {
@ -104,7 +99,7 @@ auto Connection::watchdog() -> void
} }
else else
{ {
write_irc("PING", "watchdog"); send_ping("watchdog");
stalled_ = true; stalled_ = true;
watchdog(); watchdog();
} }
@ -119,7 +114,7 @@ auto Connection::watchdog_activity() -> void
} }
/// Parse IRC message line and dispatch it to the ircmsg slot. /// Parse IRC message line and dispatch it to the ircmsg slot.
auto Connection::dispatch_line(char *line, bool flush) -> void auto Connection::dispatch_line(char *line) -> void
{ {
const auto msg = parse_irc_message(line); const auto msg = parse_irc_message(line);
const auto recognized = IrcCommandHash::in_word_set(msg.command.data(), msg.command.size()); const auto recognized = IrcCommandHash::in_word_set(msg.command.data(), msg.command.size());
@ -135,7 +130,7 @@ auto Connection::dispatch_line(char *line, bool flush) -> void
// Respond to pings immediate and discard // Respond to pings immediate and discard
case IrcCommand::PING: case IrcCommand::PING:
write_irc("PONG", msg.args[0]); send_pong(msg.args[0]);
break; break;
// Unknown message generate warnings but do not dispatch // Unknown message generate warnings but do not dispatch
@ -144,19 +139,27 @@ auto Connection::dispatch_line(char *line, bool flush) -> void
BOOST_LOG_TRIVIAL(warning) << "Unrecognized command: " << msg.command << " " << msg.args.size(); BOOST_LOG_TRIVIAL(warning) << "Unrecognized command: " << msg.command << " " << msg.args.size();
break; break;
case IrcCommand::AUTHENTICATE:
on_authenticate(msg.args[0]);
break;
// Server notice generate snote events but not IRC command events
case IrcCommand::NOTICE:
if (auto match = snoteCore.match(msg))
{
sig_snote(*match);
break;
}
/* FALLTHROUGH */
// Normal IRC commands // Normal IRC commands
default: default:
sig_ircmsg(command, msg, flush); sig_ircmsg(command, msg);
break; break;
} }
} }
auto Connection::close() -> void auto Connection::write_line(std::string message) -> void
{
stream_.close();
}
auto Connection::write_irc(std::string message) -> void
{ {
BOOST_LOG_TRIVIAL(debug) << "SEND: " << message; BOOST_LOG_TRIVIAL(debug) << "SEND: " << message;
message += "\r\n"; message += "\r\n";
@ -174,6 +177,16 @@ auto Connection::write_irc(std::string message) -> void
} }
} }
auto Connection::close() -> void
{
stream_.close();
}
auto Connection::write_irc(std::string message) -> void
{
write_line(std::move(message));
}
auto Connection::write_irc(std::string front, std::string_view last) -> void auto Connection::write_irc(std::string front, std::string_view last) -> void
{ {
bool colon = last.starts_with(":"); bool colon = last.starts_with(":");
@ -186,7 +199,209 @@ auto Connection::write_irc(std::string front, std::string_view last) -> void
} }
front += colon ? " :" : " "; front += colon ? " :" : " ";
front += last; front += last;
write_irc(std::move(front)); write_line(std::move(front));
}
auto Connection::send_ping(std::string_view txt) -> void
{
write_irc("PING", txt);
}
auto Connection::send_pong(std::string_view txt) -> void
{
write_irc("PONG", txt);
}
auto Connection::send_pass(std::string_view password) -> void
{
write_irc("PASS", password);
}
auto Connection::send_user(std::string_view user, std::string_view real) -> void
{
write_irc("USER", user, "*", "*", real);
}
auto Connection::send_nick(std::string_view nick) -> void
{
write_irc("NICK", nick);
}
auto Connection::send_cap_ls() -> void
{
write_irc("CAP", "LS", "302");
}
auto Connection::send_cap_end() -> void
{
write_irc("CAP", "END");
}
auto Connection::send_cap_req(std::string_view caps) -> void
{
write_irc("CAP", "REQ", caps);
}
auto Connection::send_privmsg(std::string_view target, std::string_view message) -> void
{
write_irc("PRIVMSG", target, message);
}
auto Connection::send_notice(std::string_view target, std::string_view message) -> void
{
write_irc("NOTICE", target, message);
}
auto Connection::send_wallops(std::string_view message) -> void
{
write_irc("WALLOPS", message);
}
auto Connection::send_names(std::string_view channel) -> void
{
write_irc("NAMES", channel);
}
auto Connection::send_map() -> void
{
write_irc("MAP");
}
auto Connection::send_get_topic(std::string_view channel) -> void
{
write_irc("TOPIC", channel);
}
auto Connection::send_set_topic(std::string_view channel, std::string_view message) -> void
{
write_irc("TOPIC", channel, message);
}
auto Connection::send_testline(std::string_view target) -> void
{
write_irc("TESTLINE", target);
}
auto Connection::send_masktrace_gecos(std::string_view target, std::string_view gecos) -> void
{
write_irc("MASKTRACE", target, gecos);
}
auto Connection::send_masktrace(std::string_view target) -> void
{
write_irc("MASKTRACE", target);
}
auto Connection::send_testmask_gecos(std::string_view target, std::string_view gecos) -> void
{
write_irc("TESTMASK", target, gecos);
}
auto Connection::send_testmask(std::string_view target) -> void
{
write_irc("TESTMASK", target);
}
auto Connection::send_authenticate(std::string_view message) -> void
{
write_irc("AUTHENTICATE", message);
}
auto Connection::send_join(std::string_view channel) -> void
{
write_irc("JOIN", channel);
}
auto Connection::send_challenge(std::string_view message) -> void
{
write_irc("CHALLENGE", message);
}
auto Connection::send_oper(std::string_view user, std::string_view pass) -> void
{
write_irc("OPER", user, pass);
}
auto Connection::send_kick(std::string_view channel, std::string_view nick, std::string_view reason) -> void
{
write_irc("KICK", channel, nick, reason);
}
auto Connection::send_kill(std::string_view nick, std::string_view reason) -> void
{
write_irc("KILL", nick, reason);
}
auto Connection::send_quit(std::string_view message) -> void
{
write_irc("QUIT", message);
}
auto Connection::send_whois(std::string_view arg1) -> void
{
write_irc("WHOIS", arg1);
}
auto Connection::send_whois_remote(std::string_view arg1, std::string_view arg2) -> void
{
write_irc("WHOIS", arg1, arg2);
}
auto Connection::on_authenticate(const std::string_view chunk) -> void
{
if (chunk != "+"sv)
{
authenticate_buffer_ += chunk;
}
if (chunk.size() != 400)
{
std::string decoded;
decoded.resize(mybase64::decoded_size(authenticate_buffer_.size()));
std::size_t len;
if (mybase64::decode(authenticate_buffer_, decoded.data(), &len))
{
decoded.resize(len);
sig_authenticate(decoded);
}
else
{
BOOST_LOG_TRIVIAL(debug) << "Invalid AUTHENTICATE base64"sv;
send_authenticate("*"sv); // abort SASL
}
authenticate_buffer_.clear();
}
else if (authenticate_buffer_.size() > 1024)
{
BOOST_LOG_TRIVIAL(debug) << "AUTHENTICATE buffer overflow"sv;
authenticate_buffer_.clear();
send_authenticate("*"sv); // abort SASL
}
}
auto Connection::send_authenticate_abort() -> void
{
send_authenticate("*");
}
auto Connection::send_authenticate_encoded(std::string_view body) -> void
{
std::string encoded(mybase64::encoded_size(body.size()), 0);
mybase64::encode(body, encoded.data());
for (size_t lo = 0; lo < encoded.size(); lo += 400)
{
const auto hi = std::min(lo + 400, encoded.size());
const std::string_view chunk{encoded.begin() + lo, encoded.begin() + hi};
send_authenticate(chunk);
}
if (encoded.size() % 400 == 0)
{
send_authenticate("+"sv);
}
} }
static static
@ -289,23 +504,6 @@ auto build_ssl_context(
return ssl_context; return ssl_context;
} }
static auto peer_fingerprint(X509 *cer) -> std::string
{
std::ostringstream os;
std::vector<std::uint8_t> result;
EVP_MD *md_used;
if (auto digest = X509_digest_sig(cer, &md_used, nullptr))
{
os << EVP_MD_name(md_used) << ":" << std::hex << std::setfill('0');
EVP_MD_free(md_used);
for (int i = 0; i < digest->length; ++i) {
os << std::setw(2) << static_cast<unsigned>(digest->data[i]);
}
ASN1_OCTET_STRING_free(digest);
}
return os.str();
}
auto Connection::connect( auto Connection::connect(
Settings settings Settings settings
) -> boost::asio::awaitable<void> ) -> boost::asio::awaitable<void>
@ -316,10 +514,6 @@ auto Connection::connect(
const auto self = shared_from_this(); const auto self = shared_from_this();
const size_t irc_buffer_size = 32'768; const size_t irc_buffer_size = 32'768;
boost::asio::ip::tcp::endpoint socket_endpoint;
std::optional<boost::asio::ip::tcp::endpoint> socks_endpoint;
std::string fingerprint;
{ {
// Name resolution // Name resolution
auto resolver = boost::asio::ip::tcp::resolver{stream_.get_executor()}; auto resolver = boost::asio::ip::tcp::resolver{stream_.get_executor()};
@ -330,37 +524,13 @@ auto Connection::connect(
// Connect to the IRC server // Connect to the IRC server
auto& socket = stream_.reset(); auto& socket = stream_.reset();
const auto endpoint = co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
// If we're going to use SOCKS then the TCP connection host is actually the socks BOOST_LOG_TRIVIAL(debug) << "CONNECTED: " << endpoint;
// server and then the IRC server gets passed over the SOCKS protocol
auto const use_socks = not settings.socks_host.empty() && settings.socks_port != 0;
if (use_socks)
{
std::swap(settings.host, settings.socks_host);
std::swap(settings.port, settings.socks_port);
}
socket_endpoint = co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
BOOST_LOG_TRIVIAL(debug) << "CONNECTED: " << socket_endpoint;
// Set socket options // Set socket options
socket.set_option(boost::asio::ip::tcp::no_delay(true)); socket.set_option(boost::asio::ip::tcp::no_delay(true));
set_buffer_size(socket, irc_buffer_size); set_buffer_size(socket, irc_buffer_size);
set_cloexec(socket.native_handle()); set_cloexec(socket.native_handle());
// Optionally negotiate SOCKS connection
if (use_socks)
{
auto auth = not settings.socks_user.empty() || not settings.socks_pass.empty()
? socks5::Auth{socks5::UsernamePasswordCredential{settings.socks_user, settings.socks_pass}}
: socks5::Auth{socks5::NoCredential{}};
socks_endpoint = co_await socks5::async_connect(
socket,
settings.socks_host, settings.socks_port, std::move(auth),
boost::asio::use_awaitable
);
}
} }
if (settings.tls) if (settings.tls)
@ -385,39 +555,25 @@ auto Connection::connect(
} }
co_await stream.async_handshake(stream.client, boost::asio::use_awaitable); co_await stream.async_handshake(stream.client, boost::asio::use_awaitable);
const auto cer = SSL_get0_peer_certificate(stream.native_handle());
fingerprint = peer_fingerprint(cer);
} }
sig_connect(socket_endpoint, socks_endpoint, std::move(fingerprint)); sig_connect();
watchdog(); watchdog();
for (LineBuffer buffer{irc_buffer_size};;) for (LineBuffer buffer{irc_buffer_size};;)
{ {
boost::system::error_code error; boost::system::error_code error;
auto const chunk = buffer.prepare(); const auto n = co_await stream_.async_read_some(buffer.get_buffer(), boost::asio::redirect_error(boost::asio::use_awaitable, error));
if (chunk.size() == 0) break;
const auto n = co_await stream_.async_read_some(chunk, boost::asio::redirect_error(boost::asio::use_awaitable, error));
if (error) if (error)
{ {
break; break;
} }
buffer.commit(n); buffer.add_bytes(n, [this](char *line) {
BOOST_LOG_TRIVIAL(debug) << "RECV: " << line;
auto line = buffer.next_nonempty_line();
if (line)
{
watchdog_activity(); watchdog_activity();
do dispatch_line(line);
{ });
BOOST_LOG_TRIVIAL(debug) << "RECV: " << line;
const auto next_line = buffer.next_nonempty_line();
dispatch_line(line, next_line == nullptr);
line = next_line;
} while (line);
}
buffer.shift();
} }
watchdog_timer_.cancel(); watchdog_timer_.cancel();
@ -444,8 +600,10 @@ auto Connection::start(Settings settings) -> void
// Disconnect all slots to avoid circular references // Disconnect all slots to avoid circular references
self->sig_connect.disconnect_all_slots(); self->sig_connect.disconnect_all_slots();
self->sig_ircmsg.disconnect_all_slots(); self->sig_ircmsg.disconnect_all_slots();
self->sig_snote.disconnect_all_slots();
self->sig_authenticate.disconnect_all_slots();
self->sig_disconnect(e); self->sig_disconnect();
self->sig_disconnect.disconnect_all_slots(); self->sig_disconnect.disconnect_all_slots();
}); });
} }

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "myirc/client.hpp" #include "connection.hpp"
#include "myirc/ref.hpp" #include "ref.hpp"
#include <boost/signals2/connection.hpp> #include <boost/signals2/connection.hpp>
@ -14,7 +14,7 @@ namespace myirc {
class Challenge : std::enable_shared_from_this<Challenge> class Challenge : std::enable_shared_from_this<Challenge>
{ {
Ref<EVP_PKEY> key_; Ref<EVP_PKEY> key_;
std::shared_ptr<Client> client_; std::shared_ptr<Connection> connection_;
boost::signals2::scoped_connection slot_; boost::signals2::scoped_connection slot_;
std::string buffer_; std::string buffer_;
@ -22,14 +22,14 @@ class Challenge : std::enable_shared_from_this<Challenge>
auto finish_challenge() -> void; auto finish_challenge() -> void;
public: public:
Challenge(Ref<EVP_PKEY>, std::shared_ptr<Client>); Challenge(Ref<EVP_PKEY>, std::shared_ptr<Connection>);
/// @brief Starts the CHALLENGE protocol. /// @brief Starts the CHALLENGE protocol.
/// @param connection Registered connection. /// @param connection Registered connection.
/// @param user Operator username /// @param user Operator username
/// @param key Operator private RSA key /// @param key Operator private RSA key
/// @return Handle to the challenge object. /// @return Handle to the challenge object.
static auto start(std::shared_ptr<Client>, std::string_view user, Ref<EVP_PKEY> key) -> std::shared_ptr<Challenge>; static auto start(std::shared_ptr<Connection>, std::string_view user, Ref<EVP_PKEY> key) -> std::shared_ptr<Challenge>;
}; };
} // namespace myirc } // namespace myirc

View File

@ -50,12 +50,6 @@ class Client
std::unordered_map<std::string, std::string> caps_available_; std::unordered_map<std::string, std::string> caps_available_;
std::unordered_set<std::string> caps_; std::unordered_set<std::string> caps_;
// AUTHENTICATE support
std::string authenticate_buffer_;
auto on_authenticate(std::string_view) -> void;
auto on_authenticate_chunk(std::string_view) -> void;
auto on_welcome(const IrcMsg &irc) -> void; auto on_welcome(const IrcMsg &irc) -> void;
auto on_isupport(const IrcMsg &irc) -> void; auto on_isupport(const IrcMsg &irc) -> void;
auto on_nick(const IrcMsg &irc) -> void; auto on_nick(const IrcMsg &irc) -> void;
@ -65,14 +59,14 @@ class Client
auto on_part(const IrcMsg &irc) -> void; auto on_part(const IrcMsg &irc) -> void;
auto on_mode(const IrcMsg &irc) -> void; auto on_mode(const IrcMsg &irc) -> void;
auto on_cap(const IrcMsg &irc) -> void; auto on_cap(const IrcMsg &irc) -> void;
auto on_authenticate(std::string_view) -> void;
auto on_registered() -> void; auto on_registered() -> void;
auto on_chat(bool, const IrcMsg &irc, bool flush) -> void; auto on_chat(bool, const IrcMsg &irc) -> void;
public: public:
boost::signals2::signal<void()> sig_registered; boost::signals2::signal<void()> sig_registered;
boost::signals2::signal<void(const std::unordered_map<std::string, std::string> &)> sig_cap_ls; boost::signals2::signal<void(const std::unordered_map<std::string, std::string> &)> sig_cap_ls;
boost::signals2::signal<void(const Chat &, bool flush)> sig_chat; boost::signals2::signal<void(const Chat &)> sig_chat;
boost::signals2::signal<void(SnoteMatch &, bool flush)> sig_snote;
Client(std::shared_ptr<Connection> connection) Client(std::shared_ptr<Connection> connection)
: connection_{std::move(connection)} : connection_{std::move(connection)}
@ -107,38 +101,6 @@ public:
auto casemap_compare(std::string_view, std::string_view) const -> int; auto casemap_compare(std::string_view, std::string_view) const -> int;
auto shutdown() -> void; auto shutdown() -> void;
auto send_ping(std::string_view) -> void;
auto send_pong(std::string_view) -> void;
auto send_pass(std::string_view) -> void;
auto send_user(std::string_view, std::string_view) -> void;
auto send_nick(std::string_view) -> void;
auto send_join(std::string_view) -> void;
auto send_names(std::string_view channel) -> void;
auto send_kick(std::string_view, std::string_view, std::string_view) -> void;
auto send_kill(std::string_view, std::string_view) -> void;
auto send_quit(std::string_view) -> void;
auto send_cap_ls() -> void;
auto send_cap_end() -> void;
auto send_map() -> void;
auto send_testline(std::string_view) -> void;
auto send_testmask(std::string_view) -> void;
auto send_testmask_gecos(std::string_view, std::string_view) -> void;
auto send_masktrace(std::string_view) -> void;
auto send_masktrace_gecos(std::string_view, std::string_view) -> void;
auto send_get_topic(std::string_view) -> void;
auto send_set_topic(std::string_view, std::string_view) -> void;
auto send_cap_req(std::string_view) -> void;
auto send_privmsg(std::string_view, std::string_view) -> void;
auto send_wallops(std::string_view) -> void;
auto send_notice(std::string_view, std::string_view) -> void;
auto send_authenticate(std::string_view message) -> void;
auto send_authenticate_encoded(std::string_view message) -> void;
auto send_authenticate_abort() -> void;
auto send_whois(std::string_view) -> void;
auto send_whois_remote(std::string_view, std::string_view) -> void;
auto send_challenge(std::string_view) -> void;
auto send_oper(std::string_view, std::string_view) -> void;
}; };
} // namespace myirc } // namespace myirc

View File

@ -47,33 +47,43 @@ private:
// Set false when message received. // Set false when message received.
bool stalled_; bool stalled_;
// AUTHENTICATE support
std::string authenticate_buffer_;
/// write buffers after consulting with rate limit /// write buffers after consulting with rate limit
auto write_buffers() -> void; auto write_buffers() -> void;
/// write a specific number of messages now /// write a specific number of messages now
auto write_buffers(size_t) -> void; auto write_buffers(size_t) -> void;
auto dispatch_line(char *line, bool) -> void; auto dispatch_line(char *line) -> void;
static constexpr std::chrono::seconds watchdog_duration = std::chrono::seconds{30}; static constexpr std::chrono::seconds watchdog_duration = std::chrono::seconds{30};
auto watchdog() -> void; auto watchdog() -> void;
auto watchdog_activity() -> void; auto watchdog_activity() -> void;
auto connect(Settings settings) -> boost::asio::awaitable<void>; auto connect(Settings settings) -> boost::asio::awaitable<void>;
auto on_authenticate(std::string_view) -> void;
/// Build and send well-formed IRC message from individual parameters
auto write_irc(std::string) -> void;
auto write_irc(std::string, std::string_view) -> void;
template <typename... Args>
auto write_irc(std::string front, std::string_view next, Args... rest) -> void;
public: public:
boost::signals2::signal<void( boost::signals2::signal<void()> sig_connect;
boost::asio::ip::tcp::endpoint, boost::signals2::signal<void()> sig_disconnect;
std::optional<boost::asio::ip::tcp::endpoint>, boost::signals2::signal<void(IrcCommand, const IrcMsg &)> sig_ircmsg;
std::string boost::signals2::signal<void(SnoteMatch &)> sig_snote;
)> sig_connect; boost::signals2::signal<void(std::string_view)> sig_authenticate;
boost::signals2::signal<void(std::exception_ptr)> sig_disconnect;
boost::signals2::signal<void(IrcCommand, const IrcMsg &, bool flush)> sig_ircmsg;
std::unique_ptr<RateLimit> rate_limit; std::unique_ptr<RateLimit> rate_limit;
Connection(boost::asio::io_context &io); Connection(boost::asio::io_context &io);
/// Write bytes into the socket.
auto write_line(std::string message) -> void;
auto get_executor() -> boost::asio::any_io_executor auto get_executor() -> boost::asio::any_io_executor
{ {
return stream_.get_executor(); return stream_.get_executor();
@ -82,11 +92,37 @@ public:
auto start(Settings) -> void; auto start(Settings) -> void;
auto close() -> void; auto close() -> void;
/// Build and send well-formed IRC message from individual parameters auto send_ping(std::string_view) -> void;
auto write_irc(std::string) -> void; auto send_pong(std::string_view) -> void;
auto write_irc(std::string, std::string_view) -> void; auto send_pass(std::string_view) -> void;
template <typename... Args> auto send_user(std::string_view, std::string_view) -> void;
auto write_irc(std::string front, std::string_view next, Args... rest) -> void; auto send_nick(std::string_view) -> void;
auto send_join(std::string_view) -> void;
auto send_names(std::string_view channel) -> void;
auto send_kick(std::string_view, std::string_view, std::string_view) -> void;
auto send_kill(std::string_view, std::string_view) -> void;
auto send_quit(std::string_view) -> void;
auto send_cap_ls() -> void;
auto send_cap_end() -> void;
auto send_map() -> void;
auto send_testline(std::string_view) -> void;
auto send_testmask(std::string_view) -> void;
auto send_testmask_gecos(std::string_view, std::string_view) -> void;
auto send_masktrace(std::string_view) -> void;
auto send_masktrace_gecos(std::string_view, std::string_view) -> void;
auto send_get_topic(std::string_view) -> void;
auto send_set_topic(std::string_view, std::string_view) -> void;
auto send_cap_req(std::string_view) -> void;
auto send_privmsg(std::string_view, std::string_view) -> void;
auto send_wallops(std::string_view) -> void;
auto send_notice(std::string_view, std::string_view) -> void;
auto send_authenticate(std::string_view message) -> void;
auto send_authenticate_encoded(std::string_view message) -> void;
auto send_authenticate_abort() -> void;
auto send_whois(std::string_view) -> void;
auto send_whois_remote(std::string_view, std::string_view) -> void;
auto send_challenge(std::string_view) -> void;
auto send_oper(std::string_view, std::string_view) -> void;
}; };
template <typename... Args> template <typename... Args>

View File

@ -238,7 +238,7 @@ auto Wait<Ts...>::await_suspend(std::coroutine_handle<irc_promise> handle) -> vo
const auto tuple_size = std::tuple_size_v<decltype(modes_)>; const auto tuple_size = std::tuple_size_v<decltype(modes_)>;
start_modes(std::make_index_sequence<tuple_size>{}); start_modes(std::make_index_sequence<tuple_size>{});
disconnect_slot_ = get_connection().sig_disconnect.connect([this](auto) { disconnect_slot_ = get_connection().sig_disconnect.connect([this]() {
handle_.resume(); handle_.resume();
}); });
} }

View File

@ -12,6 +12,8 @@
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <algorithm>
#include <concepts>
#include <vector> #include <vector>
namespace myirc { namespace myirc {
@ -22,13 +24,11 @@ namespace myirc {
*/ */
class LineBuffer class LineBuffer
{ {
std::vector<char> buffer_; std::vector<char> buffer;
// [std::begin(buffer), end_) contains buffered data // [buffer.begin(), end_) contains buffered data
// [end_, std::end(buffer)) is available buffer space // [end_, buffer.end()) is available buffer space
decltype(buffer_)::iterator start_; std::vector<char>::iterator end_;
decltype(buffer_)::iterator search_;
decltype(buffer_)::iterator end_;
public: public:
/** /**
@ -37,27 +37,19 @@ public:
* @param n Buffer size * @param n Buffer size
*/ */
LineBuffer(std::size_t n) LineBuffer(std::size_t n)
: buffer_(n) : buffer(n)
, start_{buffer_.begin()} , end_{buffer.begin()}
, search_{buffer_.begin()}
, end_{buffer_.begin()}
{ {
} }
// can't copy the iterator member safely
LineBuffer(LineBuffer const&) = delete;
LineBuffer(LineBuffer&&) = delete;
auto operator=(LineBuffer const&) -> LineBuffer& = delete;
auto operator=(LineBuffer&&) -> LineBuffer& = delete;
/** /**
* @brief Get the available buffer space * @brief Get the available buffer space
* *
* @return boost::asio::mutable_buffer * @return boost::asio::mutable_buffer
*/ */
auto prepare() -> boost::asio::mutable_buffer auto get_buffer() -> boost::asio::mutable_buffer
{ {
return boost::asio::buffer(&*end_, std::distance(end_, buffer_.end())); return boost::asio::buffer(&*end_, std::distance(end_, buffer.end()));
} }
/** /**
@ -66,39 +58,48 @@ public:
* The first n bytes of the buffer will be considered to be * The first n bytes of the buffer will be considered to be
* populated. The line callback function will be called once * populated. The line callback function will be called once
* per completed line. Those lines are removed from the buffer * per completed line. Those lines are removed from the buffer
* and the is ready for additional calls to prepare and * and the is ready for additional calls to get_buffer and
* commit. * add_bytes.
* *
* @param n Bytes written to the last call of prepare * @param n Bytes written to the last call of get_buffer
* @param line_cb Callback function to run on each completed line * @param line_cb Callback function to run on each completed line
*/ */
auto commit(std::size_t const n) -> void auto add_bytes(std::size_t n, std::invocable<char *> auto line_cb) -> void
{ {
const auto start = end_;
std::advance(end_, n); std::advance(end_, n);
// new data is now located in [start, end_)
// cursor marks the beginning of the current line
auto cursor = buffer.begin();
for (auto nl = std::find(start, end_, '\n');
nl != end_;
nl = std::find(cursor, end_, '\n'))
{
// Null-terminate the line. Support both \n and \r\n
if (cursor < nl && *std::prev(nl) == '\r')
{
*std::prev(nl) = '\0';
}
else
{
*nl = '\0';
}
line_cb(&*cursor);
cursor = std::next(nl);
}
// If any lines were processed, move all processed lines to
// the front of the buffer
if (cursor != buffer.begin())
{
end_ = std::move(cursor, end_, buffer.begin());
}
} }
/**
* @brief Return the next null-terminated line in the buffer
*
* This function should be repeatedly called until it returns
* nullptr. After that shift can be used to reclaim the
* previously used buffer.
*
* @return null-terminated line or nullptr if no line is ready
*/
auto next_line() -> char*;
/**
* @brief Return the next non-empty line if there is one.
*/
auto next_nonempty_line() -> char*;
/**
* @brief Reclaim used buffer space invalidating all previous
* next_line() results;
*
*/
auto shift() -> void;
}; };
} // namespace } // namespace myirc

View File

@ -42,12 +42,6 @@ struct Ref : std::unique_ptr<T, RefDeleter<T>>
/// Takes ownership of the pointer /// Takes ownership of the pointer
explicit Ref(T *x) noexcept : base{x} {} explicit Ref(T *x) noexcept : base{x} {}
/// Takes ownership of the pointer
static auto borrow(T *x) -> Ref {
RefTraits<T>::UpRef(x);
return Ref{x};
}
Ref(Ref &&ref) noexcept = default; Ref(Ref &&ref) noexcept = default;
Ref(const Ref &ref) noexcept : base{ref.get()} { Ref(const Ref &ref) noexcept : base{ref.get()} {
if (*this) { if (*this) {

View File

@ -260,8 +260,8 @@ struct RecognizedCommand {
ACCOUNT, IrcCommand::ACCOUNT, 1, 1 ACCOUNT, IrcCommand::ACCOUNT, 1, 1
AUTHENTICATE, IrcCommand::AUTHENTICATE, 1, 1 AUTHENTICATE, IrcCommand::AUTHENTICATE, 1, 1
AWAY, IrcCommand::AWAY, 0, 1 AWAY, IrcCommand::AWAY, 0, 1
BATCH, IrcCommand::BATCH, 1, 15 BATCH, IrcCommand::BATCH
BOUNCER, IrcCommand::BOUNCER, 1, 15 BOUNCER, IrcCommand::BOUNCER
CAP, IrcCommand::CAP, 1, 15 CAP, IrcCommand::CAP, 1, 15
CHGHOST, IrcCommand::CHGHOST, 2, 2 CHGHOST, IrcCommand::CHGHOST, 2, 2
ERROR, IrcCommand::ERROR, 1, 1 ERROR, IrcCommand::ERROR, 1, 1

View File

@ -1,54 +0,0 @@
#include "myirc/linebuffer.hpp"
namespace myirc {
auto LineBuffer::next_line() -> char*
{
auto const nl = std::find(search_, end_, '\n');
if (nl == end_) // no newline found, line incomplete
{
search_ = end_;
return nullptr;
}
// Null-terminate the line. Support both \n and \r\n
*(start_ < nl && *std::prev(nl) == '\r' ? std::prev(nl) : nl) = '\0';
auto const result = start_;
start_ = search_ = std::next(nl);
return &*result;
}
// Get the next complete line skipping over empty lines
auto LineBuffer::next_nonempty_line() -> char*
{
char* line;
while ((line = next_line()))
{
while (*line == ' ')
{
line++;
}
if ('\0' != *line)
{
break;
}
}
return line;
}
auto LineBuffer::shift() -> void
{
auto const first = std::begin(buffer_);
auto const gap = std::distance(start_, first);
if (gap != 0) // relocate incomplete line to front of buffer
{
end_ = std::move(start_, end_, first);
start_ = first;
std::advance(search_, gap);
}
}
} // namespace myirc

View File

@ -48,9 +48,9 @@ auto key_from_file(const std::string &filename, const std::string_view password)
if (const auto fp = fopen(filename.c_str(), "r")) if (const auto fp = fopen(filename.c_str(), "r"))
{ {
auto cb = [password](char * const buf, int const size, int) -> int { auto cb = [password](char * const buf, int const size, int) -> int {
if (std::cmp_less(size, password.size())) { return -1; } if (size < password.size()) { return -1; }
std::copy(password.begin(), password.end(), buf); std::copy(password.begin(), password.end(), buf);
return static_cast<int>(password.size()); return password.size();
}; };
key.reset(PEM_read_PrivateKey(fp, nullptr, CCallback<decltype(cb)>::invoke, &cb)); key.reset(PEM_read_PrivateKey(fp, nullptr, CCallback<decltype(cb)>::invoke, &cb));

View File

@ -29,7 +29,7 @@ auto Registration::on_connect() -> void
}); });
slot_ = connection.sig_ircmsg.connect( slot_ = connection.sig_ircmsg.connect(
[self = shared_from_this()](const auto cmd, auto &msg, auto) [self = shared_from_this()](const auto cmd, auto &msg)
{ {
self->on_ircmsg(cmd, msg); self->on_ircmsg(cmd, msg);
} }
@ -37,10 +37,10 @@ auto Registration::on_connect() -> void
if (not settings_.password.empty()) if (not settings_.password.empty())
{ {
client_->send_pass(settings_.password); connection.send_pass(settings_.password);
} }
client_->send_user(settings_.username, settings_.realname); connection.send_user(settings_.username, settings_.realname);
client_->send_nick(settings_.nickname); connection.send_nick(settings_.nickname);
} }
auto Registration::on_cap_list(const std::unordered_map<std::string, std::string> &caps) -> void auto Registration::on_cap_list(const std::unordered_map<std::string, std::string> &caps) -> void
@ -79,13 +79,13 @@ auto Registration::on_cap_list(const std::unordered_map<std::string, std::string
if (not request.empty()) if (not request.empty())
{ {
request.pop_back(); // trailing space request.pop_back(); // trailing space
client_->send_cap_req(request); client_->get_connection().send_cap_req(request);
} }
if (do_sasl) { if (do_sasl) {
client_->start_sasl(std::move(settings_.sasl_mechanism)); client_->start_sasl(std::move(settings_.sasl_mechanism));
} else { } else {
client_->send_cap_end(); client_->get_connection().send_cap_end();
} }
} }
@ -96,7 +96,7 @@ auto Registration::start(
{ {
const auto thread = std::make_shared<Registration>(std::move(settings), std::move(client)); const auto thread = std::make_shared<Registration>(std::move(settings), std::move(client));
thread->slot_ = thread->client_->get_connection().sig_connect.connect([thread](auto, auto, auto) { thread->slot_ = thread->client_->get_connection().sig_connect.connect([thread]() {
thread->slot_.disconnect(); thread->slot_.disconnect();
thread->on_connect(); thread->on_connect();
}); });
@ -118,7 +118,7 @@ auto Registration::randomize_nick() -> void
new_nick += x < 10 ? '0' + x : 'A' + (x-10); new_nick += x < 10 ? '0' + x : 'A' + (x-10);
} }
client_->send_nick(new_nick); client_->get_connection().send_nick(new_nick);
} }
auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void
@ -140,7 +140,7 @@ auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void
case IrcCommand::RPL_SASLSUCCESS: case IrcCommand::RPL_SASLSUCCESS:
case IrcCommand::ERR_SASLFAIL: case IrcCommand::ERR_SASLFAIL:
client_->send_cap_end(); client_->get_connection().send_cap_end();
break; break;
} }
} }

View File

@ -167,7 +167,7 @@ static auto setup_database() -> hs_database_t *
expressions.reserve(n); expressions.reserve(n);
ids.reserve(n); ids.reserve(n);
for (unsigned i = 0; i < n; i++) for (std::size_t i = 0; i < n; i++)
{ {
expressions.push_back(patterns[i].expression); expressions.push_back(patterns[i].expression);
ids.push_back(i); ids.push_back(i);
@ -176,7 +176,7 @@ static auto setup_database() -> hs_database_t *
hs_database_t *db; hs_database_t *db;
hs_compile_error *error; hs_compile_error *error;
hs_platform_info_t *platform = nullptr; // target current platform hs_platform_info_t *platform = nullptr; // target current platform
switch (hs_compile_multi(expressions.data(), flags.data(), ids.data(), static_cast<unsigned>(expressions.size()), HS_MODE_BLOCK, platform, &db, &error)) switch (hs_compile_multi(expressions.data(), flags.data(), ids.data(), expressions.size(), HS_MODE_BLOCK, platform, &db, &error))
{ {
case HS_COMPILER_ERROR: { case HS_COMPILER_ERROR: {
std::string msg = std::to_string(error->expression) + ": " + error->message; std::string msg = std::to_string(error->expression) + ": " + error->message;
@ -225,8 +225,7 @@ auto SnoteCore::match(const IrcMsg &msg) -> std::optional<SnoteMatch>
const auto scan_result = hs_scan( const auto scan_result = hs_scan(
db_.get(), db_.get(),
message.data(), message.data(), message.size(),
static_cast<unsigned>(message.size()),
0, // no flags 0, // no flags
scratch_.get(), scratch_.get(),
CCallback<decltype(cb)>::invoke, &cb CCallback<decltype(cb)>::invoke, &cb