Compare commits
10 Commits
53771396ca
...
main
Author | SHA1 | Date | |
---|---|---|---|
7793e8b02c | |||
04a092b9a3 | |||
4fc8d4d49c | |||
44ef4c0689 | |||
847a149e12 | |||
2b4bb1f071 | |||
5f2439e5af | |||
5aec8397bb | |||
39a4d84a54 | |||
5cfb47ce92 |
@@ -11,7 +11,6 @@
|
|||||||
#include "myirc/ref.hpp"
|
#include "myirc/ref.hpp"
|
||||||
#include "myirc/irc_coroutine.hpp"
|
#include "myirc/irc_coroutine.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/log/expressions.hpp>
|
#include <boost/log/expressions.hpp>
|
||||||
@@ -22,10 +21,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
using myirc::SaslMechanism;
|
|
||||||
using myirc::SaslPlain;
|
|
||||||
using myirc::SaslExternal;
|
|
||||||
using myirc::SaslEcdsa;
|
|
||||||
using myirc::Bot;
|
using myirc::Bot;
|
||||||
using myirc::Client;
|
using myirc::Client;
|
||||||
using myirc::Connection;
|
using myirc::Connection;
|
||||||
@@ -33,34 +28,7 @@ using myirc::Registration;
|
|||||||
using myirc::Challenge;
|
using myirc::Challenge;
|
||||||
using myirc::Ref;
|
using myirc::Ref;
|
||||||
|
|
||||||
auto configure_sasl(const Settings &settings) -> std::unique_ptr<SaslMechanism>
|
static auto start_irc(
|
||||||
{
|
|
||||||
if (settings.sasl_mechanism == "PLAIN" &&
|
|
||||||
not settings.sasl_authcid.empty()
|
|
||||||
) {
|
|
||||||
return std::make_unique<SaslPlain>(
|
|
||||||
settings.sasl_authcid,
|
|
||||||
settings.sasl_authzid,
|
|
||||||
settings.sasl_password);
|
|
||||||
|
|
||||||
} else if (settings.sasl_mechanism == "EXTERNAL") {
|
|
||||||
return std::make_unique<SaslExternal>(settings.sasl_authzid);
|
|
||||||
|
|
||||||
} else if (
|
|
||||||
settings.sasl_mechanism == "ECDSA" &&
|
|
||||||
not settings.sasl_authcid.empty() &&
|
|
||||||
not settings.sasl_key_file.empty()
|
|
||||||
) {
|
|
||||||
if (auto sasl_key = myirc::key_from_file(settings.sasl_key_file, settings.sasl_key_password))
|
|
||||||
return std::make_unique<SaslEcdsa>(
|
|
||||||
settings.sasl_authcid,
|
|
||||||
settings.sasl_authzid,
|
|
||||||
std::move(sasl_key));
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto start(
|
|
||||||
boost::asio::io_context &io,
|
boost::asio::io_context &io,
|
||||||
const Settings &settings,
|
const Settings &settings,
|
||||||
std::shared_ptr<Webhooks> webhook
|
std::shared_ptr<Webhooks> webhook
|
||||||
@@ -93,24 +61,24 @@ static auto start(
|
|||||||
// 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, connection, key = std::move(key)]() {
|
client->sig_registered.connect([&settings, client, key = std::move(key)]() {
|
||||||
Challenge::start(connection, settings.challenge_username, key);
|
Challenge::start(client, settings.challenge_username, key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client->sig_registered.connect([connection, webhook]() {
|
client->sig_registered.connect([client, webhook]() {
|
||||||
webhook->set_connection(connection);
|
webhook->set_client(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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]() {
|
[&io, &settings, connection, webhook](auto) {
|
||||||
webhook->clear_connection();
|
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);
|
||||||
timer->async_wait([&io, &settings, timer, webhook](auto) { start(io, settings, webhook); });
|
timer->async_wait([&io, &settings, timer, webhook](auto) { start_irc(io, settings, webhook); });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -118,7 +86,11 @@ static auto start(
|
|||||||
bot->sig_command.connect([webhook, connection](const Bot::Command &cmd) {
|
bot->sig_command.connect([webhook, connection](const Bot::Command &cmd) {
|
||||||
auto cursor = webhook_commands.find(std::string{cmd.command});
|
auto cursor = webhook_commands.find(std::string{cmd.command});
|
||||||
if (cursor != webhook_commands.end()) {
|
if (cursor != webhook_commands.end()) {
|
||||||
|
try {
|
||||||
cursor->second(webhook, cmd);
|
cursor->second(webhook, cmd);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Command handler failed: " << e.what();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,9 +127,7 @@ auto main(int argc, char *argv[]) -> int
|
|||||||
}
|
}
|
||||||
const auto settings = get_settings(argv[1]);
|
const auto settings = get_settings(argv[1]);
|
||||||
auto io = boost::asio::io_context{};
|
auto io = boost::asio::io_context{};
|
||||||
|
|
||||||
auto webhooks = start_webhook(io, argv[2]);
|
auto webhooks = start_webhook(io, argv[2]);
|
||||||
|
start_irc(io, settings, webhooks);
|
||||||
start(io, settings, webhooks);
|
|
||||||
io.run();
|
io.run();
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <myirc/openssl_utils.hpp>
|
||||||
|
|
||||||
#define TOML_ENABLE_FORMATTERS 0
|
#define TOML_ENABLE_FORMATTERS 0
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
|
|
||||||
@@ -29,3 +31,31 @@ auto Settings::from_stream(std::istream &in) -> Settings
|
|||||||
.use_tls = config["use_tls"].value_or(false),
|
.use_tls = config["use_tls"].value_or(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto configure_sasl(const Settings &settings) -> std::unique_ptr<myirc::SaslMechanism>
|
||||||
|
{
|
||||||
|
if (settings.sasl_mechanism == "PLAIN" &&
|
||||||
|
not settings.sasl_authcid.empty()
|
||||||
|
) {
|
||||||
|
return std::make_unique<myirc::SaslPlain>(
|
||||||
|
settings.sasl_authcid,
|
||||||
|
settings.sasl_authzid,
|
||||||
|
settings.sasl_password);
|
||||||
|
|
||||||
|
} else if (settings.sasl_mechanism == "EXTERNAL") {
|
||||||
|
return std::make_unique<myirc::SaslExternal>(settings.sasl_authzid);
|
||||||
|
|
||||||
|
} else if (
|
||||||
|
settings.sasl_mechanism == "ECDSA" &&
|
||||||
|
not settings.sasl_authcid.empty() &&
|
||||||
|
not settings.sasl_key_file.empty()
|
||||||
|
) {
|
||||||
|
if (auto sasl_key = myirc::key_from_file(settings.sasl_key_file, settings.sasl_key_password))
|
||||||
|
return std::make_unique<myirc::SaslEcdsa>(
|
||||||
|
settings.sasl_authcid,
|
||||||
|
settings.sasl_authzid,
|
||||||
|
std::move(sasl_key));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <myirc/sasl_mechanism.hpp>
|
||||||
|
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -32,3 +34,5 @@ struct Settings
|
|||||||
|
|
||||||
static auto from_stream(std::istream &in) -> Settings;
|
static auto from_stream(std::istream &in) -> Settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto configure_sasl(const Settings &settings) -> std::unique_ptr<myirc::SaslMechanism>;
|
||||||
|
353
driver/web.cpp
353
driver/web.cpp
@@ -15,13 +15,20 @@
|
|||||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||||
namespace websocket = beast::websocket;
|
|
||||||
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
std::map<std::string, void(*)(std::shared_ptr<Webhooks>, const ProjectSettings &, std::string_view, const boost::json::object &)> formatters {
|
||||||
|
{"push", [](std::shared_ptr<Webhooks> webhooks, const ProjectSettings &project, std::string_view full_name, const boost::json::object &body) {
|
||||||
|
webhooks->send_notice(project.channel, "push");
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used as the completion handler for coroutines in this module to print
|
||||||
|
// failure reasons to the log.
|
||||||
auto report_error(std::exception_ptr eptr) -> void
|
auto report_error(std::exception_ptr eptr) -> void
|
||||||
{
|
{
|
||||||
if (eptr)
|
if (eptr)
|
||||||
@@ -32,12 +39,17 @@ auto report_error(std::exception_ptr eptr) -> void
|
|||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(error) << "An error occurred: " << e.what();
|
BOOST_LOG_TRIVIAL(error) << "HTTP coroutine failed: " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto simple_response(http::status status, unsigned version, bool keep_alive) -> http::message_generator
|
// Construct a simple, empty reply using the given status code.
|
||||||
|
auto simple_response(
|
||||||
|
http::status status,
|
||||||
|
unsigned version,
|
||||||
|
bool keep_alive
|
||||||
|
) -> http::message_generator
|
||||||
{
|
{
|
||||||
http::response<http::string_body> res{status, version};
|
http::response<http::string_body> res{status, version};
|
||||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
@@ -46,12 +58,13 @@ static auto simple_response(http::status status, unsigned version, bool keep_ali
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto compute_signature(const std::string_view secret, const std::string_view body) -> std::string
|
// Compute the expected signature string for the POST body.
|
||||||
|
auto compute_signature(const std::string_view secret, const std::string_view body) -> std::string
|
||||||
{
|
{
|
||||||
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(), secret.size(), reinterpret_cast<const unsigned char *>(body.data()), body.size(), digest, &digest_length);
|
HMAC(EVP_sha256(), secret.data(), static_cast<int>(secret.size()), reinterpret_cast<const unsigned char *>(body.data()), body.size(), digest, &digest_length);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "sha256=";
|
ss << "sha256=";
|
||||||
@@ -63,23 +76,37 @@ static auto compute_signature(const std::string_view secret, const std::string_v
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto process_event(
|
// This event is ready to actually announce
|
||||||
|
auto announce_event(
|
||||||
|
std::shared_ptr<Webhooks> self,
|
||||||
|
const ProjectSettings &project,
|
||||||
|
const std::string full_name,
|
||||||
|
const std::string_view event_name,
|
||||||
|
const boost::json::value event
|
||||||
|
) -> void {
|
||||||
|
const auto message = std::string{event_name} + " on " + full_name;
|
||||||
|
self->send_notice(project.channel, std::move(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if this event should be announced
|
||||||
|
auto process_event(
|
||||||
std::shared_ptr<Webhooks> self,
|
std::shared_ptr<Webhooks> self,
|
||||||
const std::string_view notify_user,
|
const std::string_view notify_user,
|
||||||
const std::string_view event,
|
const std::string event_name,
|
||||||
const boost::json::value &json
|
const boost::json::value &json
|
||||||
) -> void
|
) -> void
|
||||||
{
|
{
|
||||||
auto &project = json.as_object();
|
auto &event = json.as_object();
|
||||||
|
|
||||||
|
// Determine the project name. Repositories use: user/project. Organization events use: organization
|
||||||
std::string full_name;
|
std::string full_name;
|
||||||
if (project.contains("repository"))
|
if (event.contains("repository"))
|
||||||
{
|
{
|
||||||
full_name = std::string{project.at("repository").as_object().at("full_name").as_string()};
|
full_name = std::string{event.at("repository").as_object().at("full_name").as_string()};
|
||||||
}
|
}
|
||||||
else if (project.contains("organization"))
|
else if (event.contains("organization"))
|
||||||
{
|
{
|
||||||
full_name = std::string{project.at("organization").as_object().at("login").as_string()};
|
full_name = std::string{event.at("organization").as_object().at("login").as_string()};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -96,17 +123,21 @@ static auto process_event(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (not settings.enabled)
|
if (not settings.enabled || not settings.events.contains(event_name))
|
||||||
{
|
{
|
||||||
|
// quietly ignore events we don't care about
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto message = std::string{event} + " on " + full_name;
|
auto formatter_cursor = formatters.find(event_name);
|
||||||
self->send_notice({settings.channel, message});
|
if (formatter_cursor != formatters.end()) {
|
||||||
|
formatter_cursor->second(self, settings, full_name, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the HTTP request validating its structure and signature.
|
||||||
template <class Body, class Allocator>
|
template <class Body, class Allocator>
|
||||||
static auto handle_request(
|
auto handle_request(
|
||||||
std::shared_ptr<Webhooks> self,
|
std::shared_ptr<Webhooks> self,
|
||||||
http::request<Body, http::basic_fields<Allocator>> &&req
|
http::request<Body, http::basic_fields<Allocator>> &&req
|
||||||
) -> http::message_generator
|
) -> http::message_generator
|
||||||
@@ -162,42 +193,37 @@ static auto handle_request(
|
|||||||
return simple_response(http::status::ok, req.version(), req.keep_alive());
|
return simple_response(http::status::ok, req.version(), req.keep_alive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeatedly read HTTP requests off a socket and reply to them
|
||||||
auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> self) -> boost::asio::awaitable<void>
|
auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> self) -> boost::asio::awaitable<void>
|
||||||
{
|
{
|
||||||
beast::tcp_stream stream{std::move(socket)};
|
beast::tcp_stream stream{std::move(socket)};
|
||||||
beast::flat_buffer buffer;
|
beast::flat_buffer buffer;
|
||||||
http::request<http::string_body> req;
|
http::request<http::string_body> req;
|
||||||
|
bool keep_alive = true;
|
||||||
|
|
||||||
for (;;)
|
while (keep_alive)
|
||||||
{
|
{
|
||||||
req.clear();
|
req.clear();
|
||||||
stream.expires_after(30s);
|
stream.expires_after(30s);
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
co_await http::async_read(stream, buffer, req, net::redirect_error(net::use_awaitable, ec));
|
co_await http::async_read(stream, buffer, req, net::redirect_error(net::use_awaitable, ec));
|
||||||
if (ec == http::error::end_of_stream)
|
if (ec == http::error::end_of_stream)
|
||||||
{
|
{
|
||||||
stream.socket().shutdown(tcp::socket::shutdown_send, ec);
|
break;
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
else if (ec)
|
else if (ec)
|
||||||
{
|
{
|
||||||
throw boost::system::system_error{ec};
|
throw boost::system::system_error{ec};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto keep_alive = req.keep_alive();
|
keep_alive = req.keep_alive();
|
||||||
auto msg = handle_request(self, std::move(req));
|
auto msg = handle_request(self, std::move(req));
|
||||||
|
|
||||||
co_await beast::async_write(stream, std::move(msg), net::use_awaitable);
|
co_await beast::async_write(stream, std::move(msg), net::use_awaitable);
|
||||||
|
|
||||||
if (!keep_alive)
|
|
||||||
{
|
|
||||||
stream.socket().shutdown(tcp::socket::shutdown_send, ec);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
stream.socket().shutdown(tcp::socket::shutdown_both);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeatedly accept new connections on a listening socket
|
||||||
auto accept_loop(
|
auto accept_loop(
|
||||||
tcp::acceptor acceptor,
|
tcp::acceptor acceptor,
|
||||||
std::shared_ptr<Webhooks> self
|
std::shared_ptr<Webhooks> self
|
||||||
@@ -214,6 +240,7 @@ auto accept_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Launch the listening sockets
|
||||||
auto spawn_webhook(
|
auto spawn_webhook(
|
||||||
boost::asio::io_context &io,
|
boost::asio::io_context &io,
|
||||||
const std::shared_ptr<Webhooks> webhook
|
const std::shared_ptr<Webhooks> webhook
|
||||||
@@ -242,31 +269,32 @@ auto start_webhook(
|
|||||||
const char *webhook_settings_filename
|
const char *webhook_settings_filename
|
||||||
) -> std::shared_ptr<Webhooks>
|
) -> std::shared_ptr<Webhooks>
|
||||||
{
|
{
|
||||||
std::ifstream webhook_settings_file{webhook_settings_filename};
|
auto webhook = std::make_shared<Webhooks>(webhook_settings_filename);
|
||||||
if (!webhook_settings_file)
|
webhook->load_settings();
|
||||||
{
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file";
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto webhook_settings = toml::parse(webhook_settings_file);
|
|
||||||
WebhookSettings settings = WebhookSettings::from_toml(webhook_settings);
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Webhook settings: " << settings.to_toml();
|
|
||||||
auto webhook = std::make_shared<Webhooks>(std::move(settings), webhook_settings_filename);
|
|
||||||
boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error);
|
boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error);
|
||||||
return webhook;
|
return webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Webhooks::load_settings() -> void
|
||||||
|
{
|
||||||
|
std::ifstream webhook_settings_file{settings_filename_};
|
||||||
|
if (!webhook_settings_file)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file";
|
||||||
|
}
|
||||||
|
auto webhook_settings = toml::parse(webhook_settings_file);
|
||||||
|
settings_ = WebhookSettings::from_toml(webhook_settings);
|
||||||
|
}
|
||||||
|
|
||||||
auto Webhooks::save_settings() const -> void
|
auto Webhooks::save_settings() const -> void
|
||||||
{
|
{
|
||||||
std::ofstream webhook_settings_file{settings_file};
|
std::ofstream webhook_settings_file{settings_filename_};
|
||||||
if (!webhook_settings_file)
|
if (!webhook_settings_file)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file";
|
BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
webhook_settings_file << settings_.to_toml() << "\n";
|
||||||
webhook_settings_file << settings_.to_toml();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ProjectSettings::from_toml(const toml::table &v) -> ProjectSettings
|
auto ProjectSettings::from_toml(const toml::table &v) -> ProjectSettings
|
||||||
@@ -383,131 +411,214 @@ auto WebhookSettings::to_toml() const -> toml::table
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Either emit the event now or save it until a connection is set
|
// Either emit the event now or save it until a connection is set
|
||||||
auto Webhooks::send_notice(Notice notice) -> void
|
auto Webhooks::send_notice(std::string_view target, std::string message) -> void
|
||||||
{
|
{
|
||||||
if (connection_)
|
if (client_)
|
||||||
{
|
{
|
||||||
connection_->send_notice(notice.target, notice.message);
|
client_->send_notice(target, message);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
events_.emplace_back(std::move(notice));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Webhooks::set_connection(std::shared_ptr<myirc::Connection> connection) -> void
|
auto Webhooks::refresh_channels() const -> void
|
||||||
{
|
{
|
||||||
connection_ = std::move(connection);
|
if (not client_) return;
|
||||||
for (auto &&event : events_)
|
|
||||||
|
std::stringstream ss;
|
||||||
|
std::set<std::string_view> added;
|
||||||
|
bool first = true;
|
||||||
|
for (auto &&[_, project] : settings_.projects) {
|
||||||
|
auto &channel = project.channel;
|
||||||
|
if (!channel.empty() &&
|
||||||
|
!client_->is_on_channel(channel) &&
|
||||||
|
added.insert(channel).second)
|
||||||
{
|
{
|
||||||
connection_->send_notice(event.target, event.message);
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
ss << ",";
|
||||||
|
}
|
||||||
|
ss << channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (not first) {
|
||||||
|
client_->send_join(ss.str());
|
||||||
}
|
}
|
||||||
events_.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Webhooks::clear_connection() -> void
|
auto Webhooks::set_client(std::shared_ptr<myirc::Client> client) -> void
|
||||||
{
|
{
|
||||||
connection_.reset();
|
client_ = std::move(client);
|
||||||
|
refresh_channels();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Webhooks::clear_client() -> void
|
||||||
|
{
|
||||||
|
client_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto reply_to(std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd, std::string message) -> void
|
static auto reply_to(std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd, std::string message) -> void
|
||||||
{
|
{
|
||||||
if (cmd.target.starts_with("#"))
|
if (cmd.target.starts_with("#"))
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.target}, std::move(message)});
|
webhooks->send_notice(cmd.target, std::move(message));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.nick()}, std::move(message)});
|
webhooks->send_notice(cmd.nick(), std::move(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto authorized_for_project(const ProjectSettings &project, const std::string_view nick) -> bool
|
// Operators are authorized for all projects otherwise nickserv account names can be added to individual projects.
|
||||||
|
static auto authorized_for_project(
|
||||||
|
const myirc::Bot::Command &cmd,
|
||||||
|
const ProjectSettings &project,
|
||||||
|
const std::string_view nick
|
||||||
|
) -> bool
|
||||||
{
|
{
|
||||||
return project.authorized_accounts.find(std::string{nick}) != project.authorized_accounts.end();
|
return !cmd.oper.empty() || project.authorized_accounts.contains(std::string{nick});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Command &)> webhook_commands{
|
std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Command &)> webhook_commands{
|
||||||
{"add-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
{"announce", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
//if (cmd.oper.empty())
|
|
||||||
//{
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
std::string name, key;
|
std::string name, mode;
|
||||||
if (iss >> name >> key)
|
if (iss >> name >> mode)
|
||||||
{
|
{
|
||||||
webhooks->settings_.credentials.insert_or_assign(name, key);
|
auto &project = webhooks->settings_.projects.at(name);
|
||||||
webhooks->save_settings();
|
if (not authorized_for_project(cmd, project, cmd.account))
|
||||||
reply_to(webhooks, cmd, "Added credential " + name);
|
|
||||||
}
|
|
||||||
}},
|
|
||||||
{"drop-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
|
||||||
//if (cmd.oper.empty())
|
|
||||||
//{
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
|
||||||
std::string name;
|
|
||||||
if (iss >> name)
|
|
||||||
{
|
{
|
||||||
webhooks->settings_.credentials.erase(name);
|
|
||||||
webhooks->save_settings();
|
|
||||||
reply_to(webhooks, cmd, "Dropped credential " + name);
|
|
||||||
}
|
|
||||||
}},
|
|
||||||
{"enable-project", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
|
||||||
std::string name;
|
|
||||||
if (iss >> name)
|
|
||||||
{
|
|
||||||
auto cursor = webhooks->settings_.projects.find(name);
|
|
||||||
if (cursor == webhooks->settings_.projects.end())
|
|
||||||
{
|
|
||||||
reply_to(webhooks, cmd, "Unknown project " + name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mode == "on") {
|
||||||
auto &project = cursor->second;
|
|
||||||
|
|
||||||
if (not authorized_for_project(project, cmd.account))
|
|
||||||
{
|
|
||||||
reply_to(webhooks, cmd, "Unauthorized to enable project " + name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
project.enabled = true;
|
project.enabled = true;
|
||||||
webhooks->save_settings();
|
|
||||||
reply_to(webhooks, cmd, "Enabled project " + name);
|
reply_to(webhooks, cmd, "Enabled project " + name);
|
||||||
|
} else if (mode == "off") {
|
||||||
|
project.enabled = false;
|
||||||
|
reply_to(webhooks, cmd, "Disabled project " + name);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webhooks->save_settings();
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{"disable-project", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
{"event", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
//if (cmd.oper.empty())
|
|
||||||
//{
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
std::string name;
|
std::string name, mode;
|
||||||
if (iss >> name)
|
if (iss >> name >> mode)
|
||||||
{
|
{
|
||||||
auto cursor = webhooks->settings_.projects.find(name);
|
auto &project = webhooks->settings_.projects.at(name);
|
||||||
if (cursor == webhooks->settings_.projects.end())
|
if (not authorized_for_project(cmd, project, cmd.account))
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.nick()}, "Unknown project " + name});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &project = cursor->second;
|
if (mode == "list") {
|
||||||
|
std::stringstream ss;
|
||||||
if (not authorized_for_project(project, cmd.account))
|
ss << "Events for " << name << ":";
|
||||||
{
|
for (auto &&event : project.events) {
|
||||||
reply_to(webhooks, cmd, "Unauthorized to disable project " + name);
|
ss << " " << event;
|
||||||
|
}
|
||||||
|
reply_to(webhooks, cmd, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
project.enabled = false;
|
unsigned n_added = 0, n_removed = 0, n_skipped = 0, n_unknown = 0;
|
||||||
|
if (mode == "add") {
|
||||||
|
while (iss >> name) {
|
||||||
|
if (formatters.contains(name)) {
|
||||||
|
const auto [_, added] = project.events.insert(name);
|
||||||
|
if (added) { n_added++; } else { n_skipped++; }
|
||||||
|
} else {
|
||||||
|
n_unknown++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mode == "del") {
|
||||||
|
while (iss >> name) {
|
||||||
|
if (formatters.contains(name)) {
|
||||||
|
const auto removed = project.events.erase(name);
|
||||||
|
if (removed) { n_removed++; } else { n_skipped++; }
|
||||||
|
} else {
|
||||||
|
n_unknown++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
webhooks->save_settings();
|
webhooks->save_settings();
|
||||||
reply_to(webhooks, cmd, "Disabled project " + name);
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Events updated:";
|
||||||
|
if (n_added) { ss << " added " << n_added; }
|
||||||
|
if (n_removed) { ss << " removed " << n_removed; }
|
||||||
|
if (n_skipped) { ss << " skipped " << n_skipped; }
|
||||||
|
if (n_unknown) { ss << " unknown " << n_unknown; }
|
||||||
|
reply_to(webhooks, cmd, ss.str());
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
|
{"auth", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
|
if (cmd.oper.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
|
std::string name, mode;
|
||||||
|
if (iss >> name >> mode)
|
||||||
|
{
|
||||||
|
auto &project = webhooks->settings_.projects.at(name);
|
||||||
|
|
||||||
|
if (mode == "list") {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Authorized accounts:";
|
||||||
|
for (auto &&event : project.authorized_accounts) {
|
||||||
|
ss << " " << event;
|
||||||
|
}
|
||||||
|
reply_to(webhooks, cmd, ss.str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned n_added = 0, n_removed = 0, n_skipped = 0;
|
||||||
|
if (mode == "add") {
|
||||||
|
while (iss >> name) {
|
||||||
|
const auto [_, added] = project.authorized_accounts.insert(name);
|
||||||
|
if (added) { n_added++; } else { n_skipped++; }
|
||||||
|
}
|
||||||
|
} else if (mode == "del") {
|
||||||
|
while (iss >> name) {
|
||||||
|
const auto removed = project.authorized_accounts.erase(name);
|
||||||
|
if (removed) { n_removed++; } else { n_skipped++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webhooks->save_settings();
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Authorized accounts updated:";
|
||||||
|
if (n_added) { ss << " added " << n_added; }
|
||||||
|
if (n_removed) { ss << " removed " << n_removed; }
|
||||||
|
if (n_skipped) { ss << " skipped " << n_skipped; }
|
||||||
|
reply_to(webhooks, cmd, ss.str());
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"setchannel", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
|
if (cmd.oper.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
|
std::string name, channel;
|
||||||
|
if (iss >> name >> channel)
|
||||||
|
{
|
||||||
|
auto &project = webhooks->settings_.projects.at(name);
|
||||||
|
project.channel = channel;
|
||||||
|
webhooks->save_settings();
|
||||||
|
reply_to(webhooks, cmd, "Channel assigned");
|
||||||
|
|
||||||
|
webhooks->refresh_channels();
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"rehash", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
|
if (cmd.oper.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webhooks->load_settings();
|
||||||
|
reply_to(webhooks, cmd, "Rehashed");
|
||||||
|
}},
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <myirc/bot.hpp>
|
#include <myirc/bot.hpp>
|
||||||
#include <myirc/connection.hpp>
|
#include <myirc/client.hpp>
|
||||||
|
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
|
|
||||||
@@ -48,35 +48,27 @@ struct WebhookSettings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Webhooks {
|
class Webhooks {
|
||||||
public:
|
|
||||||
struct Notice {
|
|
||||||
std::string target;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
|
|
||||||
// IRC connection to announce on; could be empty
|
// IRC connection to announce on; could be empty
|
||||||
std::shared_ptr<myirc::Connection> connection_;
|
std::shared_ptr<myirc::Client> client_;
|
||||||
|
|
||||||
// Buffered events in case connection was inactive when event was received
|
const char * settings_filename_;
|
||||||
std::vector<Notice> events_;
|
|
||||||
|
|
||||||
const char * settings_file;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebhookSettings settings_;
|
WebhookSettings settings_;
|
||||||
|
|
||||||
Webhooks(WebhookSettings settings, const char * settings_file)
|
Webhooks(const char * settings_filename)
|
||||||
: settings_(std::move(settings))
|
: settings_filename_{settings_filename}
|
||||||
, settings_file(settings_file)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either emit the event now or save it until a connection is set
|
// Either emit the event now or save it until a connection is set
|
||||||
auto send_notice(Notice event) -> void;
|
auto send_notice(std::string_view, std::string) -> void;
|
||||||
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void;
|
auto set_client(std::shared_ptr<myirc::Client> client) -> void;
|
||||||
auto clear_connection() -> void;
|
auto clear_client() -> void;
|
||||||
auto save_settings() const -> void;
|
auto save_settings() const -> void;
|
||||||
|
auto load_settings() -> void;
|
||||||
|
auto refresh_channels() const -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<Webhooks>;
|
auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<Webhooks>;
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
@@ -35,10 +34,8 @@ 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
|
||||||
* @param outlen Output parameter for decoded length
|
* @return pointer to end of output on success
|
||||||
* @return true success
|
|
||||||
* @return false failure
|
|
||||||
*/
|
*/
|
||||||
auto decode(std::string_view input, char* output, std::size_t* outlen) -> bool;
|
auto decode(std::string_view input, char* output) -> char*;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@@ -1,29 +1,48 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
static char const* const alphabet =
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
auto cursor = std::begin(input);
|
auto cursor = std::begin(input);
|
||||||
auto const end = std::end(input);
|
auto const end = std::end(input);
|
||||||
|
|
||||||
while (end - cursor >= 3)
|
while (end - cursor >= 3)
|
||||||
{
|
{
|
||||||
uint32_t buffer = uint8_t(*cursor++);
|
std::uint32_t buffer = std::uint8_t(*cursor++);
|
||||||
buffer <<= 8; buffer |= uint8_t(*cursor++);
|
buffer <<= 8;
|
||||||
buffer <<= 8; buffer |= uint8_t(*cursor++);
|
buffer |= std::uint8_t(*cursor++);
|
||||||
|
buffer <<= 8;
|
||||||
|
buffer |= std::uint8_t(*cursor++);
|
||||||
|
|
||||||
*output++ = alphabet[(buffer >> 6 * 3) % 64];
|
*output++ = alphabet[(buffer >> 6 * 3) % 64];
|
||||||
*output++ = alphabet[(buffer >> 6 * 2) % 64];
|
*output++ = alphabet[(buffer >> 6 * 2) % 64];
|
||||||
@@ -33,8 +52,9 @@ auto encode(std::string_view const input, char* output) -> void
|
|||||||
|
|
||||||
if (cursor < end)
|
if (cursor < end)
|
||||||
{
|
{
|
||||||
uint32_t buffer = uint8_t(*cursor++) << 10;
|
std::uint32_t buffer = std::uint8_t(*cursor++) << 10;
|
||||||
if (cursor < end) buffer |= uint8_t(*cursor) << 2;
|
if (cursor < end)
|
||||||
|
buffer |= std::uint8_t(*cursor) << 2;
|
||||||
|
|
||||||
*output++ = alphabet[(buffer >> 12) % 64];
|
*output++ = alphabet[(buffer >> 12) % 64];
|
||||||
*output++ = alphabet[(buffer >> 6) % 64];
|
*output++ = alphabet[(buffer >> 6) % 64];
|
||||||
@@ -44,70 +64,39 @@ auto encode(std::string_view const input, char* output) -> void
|
|||||||
*output = '\0';
|
*output = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
auto decode(std::string_view const input, char* const output, std::size_t* const outlen) -> bool
|
auto decode(std::string_view const input, char* output) -> char*
|
||||||
{
|
{
|
||||||
static int8_t const alphabet_values[] = {
|
std::uint32_t buffer = 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, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t buffer = 1;
|
|
||||||
char* cursor = output;
|
|
||||||
|
|
||||||
for (char c : input) {
|
|
||||||
int8_t const value = alphabet_values[uint8_t(c)];
|
|
||||||
if (-1 == value) continue;
|
|
||||||
|
|
||||||
|
for (auto const c : input)
|
||||||
|
{
|
||||||
|
if (auto const value = alphabet_values[uint8_t(c)]; -1 != value)
|
||||||
|
{
|
||||||
buffer = (buffer << 6) | value;
|
buffer = (buffer << 6) | value;
|
||||||
|
if (buffer & 1 << 6 * 4)
|
||||||
if (buffer & 1<<6*4) {
|
{
|
||||||
*cursor++ = buffer >> 8*2;
|
*output++ = buffer >> 16;
|
||||||
*cursor++ = buffer >> 8*1;
|
*output++ = buffer >> 8;
|
||||||
*cursor++ = buffer >> 8*0;
|
*output++ = buffer >> 0;
|
||||||
buffer = 1;
|
buffer = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer & 1<<6*3) {
|
|
||||||
*cursor++ = buffer >> 10;
|
|
||||||
*cursor++ = buffer >> 2;
|
|
||||||
} else if (buffer & 1<<6*2) {
|
|
||||||
*cursor++ = buffer >> 4;
|
|
||||||
} else if (buffer & 1<<6*1) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
*outlen = cursor - output;
|
|
||||||
return true;
|
if (buffer & 1 << 6 * 3)
|
||||||
|
{
|
||||||
|
*output++ = buffer >> 10;
|
||||||
|
*output++ = buffer >> 2;
|
||||||
|
}
|
||||||
|
else if (buffer & 1 << 6 * 2)
|
||||||
|
{
|
||||||
|
*output++ = buffer >> 4;
|
||||||
|
}
|
||||||
|
else if (buffer & 1 << 6 * 1)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@@ -20,6 +20,7 @@ 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)
|
||||||
|
@@ -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) {
|
thread->self_->sig_chat.connect([thread](auto &chat, bool) {
|
||||||
thread->on_chat(chat);
|
thread->on_chat(chat);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
namespace myirc {
|
namespace myirc {
|
||||||
|
|
||||||
Challenge::Challenge(Ref<EVP_PKEY> key, std::shared_ptr<Connection> connection)
|
Challenge::Challenge(Ref<EVP_PKEY> key, std::shared_ptr<Client> client)
|
||||||
: key_{std::move(key)}
|
: key_{std::move(key)}
|
||||||
, connection_{std::move(connection)}
|
, client_{std::move(client)}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
auto Challenge::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void {
|
auto Challenge::on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void {
|
||||||
@@ -31,7 +31,6 @@ 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();
|
||||||
@@ -46,9 +45,10 @@ 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);
|
||||||
|
|
||||||
if (not mybase64::decode(buffer_, reinterpret_cast<char*>(ciphertext.data()), &len))
|
auto decode_end = mybase64::decode(buffer_, reinterpret_cast<char*>(ciphertext.data()));
|
||||||
|
if (decode_end == nullptr )
|
||||||
return log_openssl_errors("Challenge base64::decode: ");
|
return log_openssl_errors("Challenge base64::decode: ");
|
||||||
ciphertext.resize(len);
|
ciphertext.resize(decode_end - reinterpret_cast<char*>(ciphertext.data()));
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
connection_->send_challenge(buffer_);
|
client_->send_challenge(buffer_);
|
||||||
buffer_.clear();
|
buffer_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Challenge::start(std::shared_ptr<Connection> connection, const std::string_view user, Ref<EVP_PKEY> ref) -> std::shared_ptr<Challenge>
|
auto Challenge::start(std::shared_ptr<Client> client, const std::string_view user, Ref<EVP_PKEY> ref) -> std::shared_ptr<Challenge>
|
||||||
{
|
{
|
||||||
auto self = std::make_shared<Challenge>(std::move(ref), connection);
|
auto self = std::make_shared<Challenge>(std::move(ref), client);
|
||||||
self->slot_ = connection->sig_ircmsg.connect([self](auto cmd, auto &msg) { self->on_ircmsg(cmd, msg); });
|
self->slot_ = client->get_connection().sig_ircmsg.connect([self](auto cmd, auto &msg, bool) { self->on_ircmsg(cmd, msg); });
|
||||||
connection->send_challenge(user);
|
client->send_challenge(user);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
247
myirc/client.cpp
247
myirc/client.cpp
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ auto Client::on_isupport(const IrcMsg &msg) -> void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Client::on_chat(bool notice, const IrcMsg &irc) -> void
|
auto Client::on_chat(bool notice, const IrcMsg &irc, bool flush) -> void
|
||||||
{
|
{
|
||||||
char status_msg = '\0';
|
char status_msg = '\0';
|
||||||
std::string_view target = irc.args[0];
|
std::string_view target = irc.args[0];
|
||||||
@@ -137,21 +138,26 @@ auto Client::on_chat(bool notice, const IrcMsg &irc) -> 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) {
|
connection->sig_ircmsg.connect([thread](auto cmd, auto &msg, bool flush) {
|
||||||
switch (cmd)
|
switch (cmd)
|
||||||
{
|
{
|
||||||
case IrcCommand::PRIVMSG:
|
case IrcCommand::PRIVMSG:
|
||||||
thread->on_chat(false, msg);
|
thread->on_chat(false, msg, flush);
|
||||||
break;
|
break;
|
||||||
case IrcCommand::NOTICE:
|
case IrcCommand::NOTICE:
|
||||||
thread->on_chat(true, msg);
|
if (auto match = snoteCore.match(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);
|
||||||
@@ -184,13 +190,19 @@ 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_authenticate.connect([thread](auto msg) {
|
connection->sig_disconnect.connect([thread](auto) {
|
||||||
thread->on_authenticate(msg);
|
thread->sig_registered.disconnect_all_slots();
|
||||||
|
thread->sig_cap_ls.disconnect_all_slots();
|
||||||
|
thread->sig_chat.disconnect_all_slots();
|
||||||
|
thread->sig_snote.disconnect_all_slots();
|
||||||
});
|
});
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
@@ -227,6 +239,11 @@ auto Client::is_channel(std::string_view name) const -> bool
|
|||||||
return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos;
|
return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Client::is_on_channel(std::string_view name) const -> bool
|
||||||
|
{
|
||||||
|
return channels_.contains(casemap(name));
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template <class... Ts>
|
template <class... Ts>
|
||||||
struct overloaded : Ts...
|
struct overloaded : Ts...
|
||||||
@@ -242,20 +259,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;
|
||||||
connection_->send_authenticate_abort();
|
send_authenticate_abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::visit(
|
std::visit(
|
||||||
overloaded{
|
overloaded{
|
||||||
[this](const std::string &reply) {
|
[this](const std::string &reply) {
|
||||||
connection_->send_authenticate_encoded(reply);
|
send_authenticate_encoded(reply);
|
||||||
},
|
},
|
||||||
[this](SaslMechanism::NoReply) {
|
[this](SaslMechanism::NoReply) {
|
||||||
connection_->send_authenticate("*"sv);
|
send_authenticate("*"sv);
|
||||||
},
|
},
|
||||||
[this](SaslMechanism::Failure) {
|
[this](SaslMechanism::Failure) {
|
||||||
connection_->send_authenticate_abort();
|
send_authenticate_abort();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sasl_mechanism_->step(body));
|
sasl_mechanism_->step(body));
|
||||||
@@ -270,11 +287,11 @@ auto Client::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void
|
|||||||
{
|
{
|
||||||
if (sasl_mechanism_)
|
if (sasl_mechanism_)
|
||||||
{
|
{
|
||||||
connection_->send_authenticate("*"sv); // abort SASL
|
send_authenticate("*"sv); // abort SASL
|
||||||
}
|
}
|
||||||
|
|
||||||
sasl_mechanism_ = std::move(mechanism);
|
sasl_mechanism_ = std::move(mechanism);
|
||||||
connection_->send_authenticate(sasl_mechanism_->mechanism_name());
|
send_authenticate(sasl_mechanism_->mechanism_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto tolower_rfc1459(int c) -> int
|
static auto tolower_rfc1459(int c) -> int
|
||||||
@@ -334,7 +351,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();
|
||||||
connection_->send_cap_ls();
|
send_cap_ls();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Client::on_cap(const IrcMsg &msg) -> void
|
auto Client::on_cap(const IrcMsg &msg) -> void
|
||||||
@@ -429,4 +446,206 @@ 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
|
||||||
|
@@ -1,12 +1,17 @@
|
|||||||
#include "myirc/connection.hpp"
|
#include "myirc/connection.hpp"
|
||||||
|
|
||||||
#include "myirc/linebuffer.hpp"
|
#include "myirc/linebuffer.hpp"
|
||||||
#include "myirc/snote.hpp"
|
#include <openssl/asn1.h>
|
||||||
|
#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 {
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ auto Connection::watchdog() -> void
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
send_ping("watchdog");
|
write_irc("PING", "watchdog");
|
||||||
stalled_ = true;
|
stalled_ = true;
|
||||||
watchdog();
|
watchdog();
|
||||||
}
|
}
|
||||||
@@ -114,7 +119,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) -> void
|
auto Connection::dispatch_line(char *line, bool flush) -> 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());
|
||||||
@@ -130,7 +135,7 @@ auto Connection::dispatch_line(char *line) -> void
|
|||||||
|
|
||||||
// Respond to pings immediate and discard
|
// Respond to pings immediate and discard
|
||||||
case IrcCommand::PING:
|
case IrcCommand::PING:
|
||||||
send_pong(msg.args[0]);
|
write_irc("PONG", msg.args[0]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Unknown message generate warnings but do not dispatch
|
// Unknown message generate warnings but do not dispatch
|
||||||
@@ -139,27 +144,19 @@ auto Connection::dispatch_line(char *line) -> 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);
|
sig_ircmsg(command, msg, flush);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Connection::write_line(std::string message) -> void
|
auto Connection::close() -> 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";
|
||||||
@@ -177,16 +174,6 @@ auto Connection::write_line(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(":");
|
||||||
@@ -199,209 +186,7 @@ auto Connection::write_irc(std::string front, std::string_view last) -> void
|
|||||||
}
|
}
|
||||||
front += colon ? " :" : " ";
|
front += colon ? " :" : " ";
|
||||||
front += last;
|
front += last;
|
||||||
write_line(std::move(front));
|
write_irc(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
|
||||||
@@ -504,6 +289,23 @@ 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>
|
||||||
@@ -514,6 +316,10 @@ 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()};
|
||||||
@@ -524,13 +330,37 @@ 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);
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "CONNECTED: " << endpoint;
|
// If we're going to use SOCKS then the TCP connection host is actually the socks
|
||||||
|
// 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)
|
||||||
@@ -555,25 +385,39 @@ 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();
|
sig_connect(socket_endpoint, socks_endpoint, std::move(fingerprint));
|
||||||
|
|
||||||
watchdog();
|
watchdog();
|
||||||
|
|
||||||
for (LineBuffer buffer{irc_buffer_size};;)
|
for (LineBuffer buffer{irc_buffer_size};;)
|
||||||
{
|
{
|
||||||
boost::system::error_code error;
|
boost::system::error_code error;
|
||||||
const auto n = co_await stream_.async_read_some(buffer.get_buffer(), boost::asio::redirect_error(boost::asio::use_awaitable, error));
|
auto const chunk = buffer.prepare();
|
||||||
|
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.add_bytes(n, [this](char *line) {
|
buffer.commit(n);
|
||||||
BOOST_LOG_TRIVIAL(debug) << "RECV: " << line;
|
|
||||||
|
auto line = buffer.next_nonempty_line();
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
watchdog_activity();
|
watchdog_activity();
|
||||||
dispatch_line(line);
|
do
|
||||||
});
|
{
|
||||||
|
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();
|
||||||
@@ -600,10 +444,8 @@ 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();
|
self->sig_disconnect(e);
|
||||||
self->sig_disconnect.disconnect_all_slots();
|
self->sig_disconnect.disconnect_all_slots();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "connection.hpp"
|
#include "myirc/client.hpp"
|
||||||
#include "ref.hpp"
|
#include "myirc/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<Connection> connection_;
|
std::shared_ptr<Client> client_;
|
||||||
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<Connection>);
|
Challenge(Ref<EVP_PKEY>, std::shared_ptr<Client>);
|
||||||
|
|
||||||
/// @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<Connection>, std::string_view user, Ref<EVP_PKEY> key) -> std::shared_ptr<Challenge>;
|
static auto start(std::shared_ptr<Client>, std::string_view user, Ref<EVP_PKEY> key) -> std::shared_ptr<Challenge>;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace myirc
|
} // namespace myirc
|
||||||
|
@@ -50,6 +50,12 @@ 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;
|
||||||
@@ -59,14 +65,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) -> void;
|
auto on_chat(bool, const IrcMsg &irc, bool flush) -> 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 &)> sig_chat;
|
boost::signals2::signal<void(const Chat &, bool flush)> 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)}
|
||||||
@@ -96,11 +102,44 @@ public:
|
|||||||
auto is_my_nick(std::string_view nick) const -> bool;
|
auto is_my_nick(std::string_view nick) const -> bool;
|
||||||
auto is_my_mask(std::string_view mask) const -> bool;
|
auto is_my_mask(std::string_view mask) const -> bool;
|
||||||
auto is_channel(std::string_view name) const -> bool;
|
auto is_channel(std::string_view name) const -> bool;
|
||||||
|
auto is_on_channel(std::string_view name) const -> bool;
|
||||||
|
|
||||||
auto casemap(std::string_view) const -> std::string;
|
auto casemap(std::string_view) const -> std::string;
|
||||||
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
|
||||||
|
@@ -47,43 +47,33 @@ 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) -> void;
|
auto dispatch_line(char *line, bool) -> 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()> sig_connect;
|
boost::signals2::signal<void(
|
||||||
boost::signals2::signal<void()> sig_disconnect;
|
boost::asio::ip::tcp::endpoint,
|
||||||
boost::signals2::signal<void(IrcCommand, const IrcMsg &)> sig_ircmsg;
|
std::optional<boost::asio::ip::tcp::endpoint>,
|
||||||
boost::signals2::signal<void(SnoteMatch &)> sig_snote;
|
std::string
|
||||||
boost::signals2::signal<void(std::string_view)> sig_authenticate;
|
)> sig_connect;
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -92,37 +82,11 @@ public:
|
|||||||
auto start(Settings) -> void;
|
auto start(Settings) -> void;
|
||||||
auto close() -> void;
|
auto close() -> void;
|
||||||
|
|
||||||
auto send_ping(std::string_view) -> void;
|
/// Build and send well-formed IRC message from individual parameters
|
||||||
auto send_pong(std::string_view) -> void;
|
auto write_irc(std::string) -> void;
|
||||||
auto send_pass(std::string_view) -> void;
|
auto write_irc(std::string, std::string_view) -> void;
|
||||||
auto send_user(std::string_view, std::string_view) -> void;
|
template <typename... Args>
|
||||||
auto send_nick(std::string_view) -> void;
|
auto write_irc(std::string front, std::string_view next, Args... rest) -> 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>
|
||||||
|
@@ -13,7 +13,6 @@ enum class IrcCommand
|
|||||||
RPL_SNOMASK,
|
RPL_SNOMASK,
|
||||||
RPL_REDIR,
|
RPL_REDIR,
|
||||||
RPL_MAP,
|
RPL_MAP,
|
||||||
RPL_MAPMORE,
|
|
||||||
RPL_MAPEND,
|
RPL_MAPEND,
|
||||||
RPL_SAVENICK,
|
RPL_SAVENICK,
|
||||||
RPL_TRACELINK,
|
RPL_TRACELINK,
|
||||||
@@ -28,7 +27,6 @@ enum class IrcCommand
|
|||||||
RPL_STATSLINKINFO,
|
RPL_STATSLINKINFO,
|
||||||
RPL_STATSCOMMANDS,
|
RPL_STATSCOMMANDS,
|
||||||
RPL_STATSCLINE,
|
RPL_STATSCLINE,
|
||||||
RPL_STATSNLINE,
|
|
||||||
RPL_STATSILINE,
|
RPL_STATSILINE,
|
||||||
RPL_STATSKLINE,
|
RPL_STATSKLINE,
|
||||||
RPL_STATSQLINE,
|
RPL_STATSQLINE,
|
||||||
@@ -36,15 +34,11 @@ enum class IrcCommand
|
|||||||
RPL_ENDOFSTATS,
|
RPL_ENDOFSTATS,
|
||||||
RPL_STATSPLINE,
|
RPL_STATSPLINE,
|
||||||
RPL_UMODEIS,
|
RPL_UMODEIS,
|
||||||
RPL_STATSFLINE,
|
|
||||||
RPL_STATSDLINE,
|
RPL_STATSDLINE,
|
||||||
RPL_SERVLIST,
|
|
||||||
RPL_SERVLISTEND,
|
|
||||||
RPL_STATSLLINE,
|
RPL_STATSLLINE,
|
||||||
RPL_STATSUPTIME,
|
RPL_STATSUPTIME,
|
||||||
RPL_STATSOLINE,
|
RPL_STATSOLINE,
|
||||||
RPL_STATSHLINE,
|
RPL_STATSHLINE,
|
||||||
RPL_STATSSLINE,
|
|
||||||
RPL_STATSXLINE,
|
RPL_STATSXLINE,
|
||||||
RPL_STATSULINE,
|
RPL_STATSULINE,
|
||||||
RPL_STATSDEBUG,
|
RPL_STATSDEBUG,
|
||||||
@@ -58,7 +52,6 @@ enum class IrcCommand
|
|||||||
RPL_ADMINLOC1,
|
RPL_ADMINLOC1,
|
||||||
RPL_ADMINLOC2,
|
RPL_ADMINLOC2,
|
||||||
RPL_ADMINEMAIL,
|
RPL_ADMINEMAIL,
|
||||||
RPL_TRACELOG,
|
|
||||||
RPL_ENDOFTRACE,
|
RPL_ENDOFTRACE,
|
||||||
RPL_LOAD2HI,
|
RPL_LOAD2HI,
|
||||||
RPL_LOCALUSERS,
|
RPL_LOCALUSERS,
|
||||||
@@ -67,11 +60,9 @@ enum class IrcCommand
|
|||||||
RPL_WHOISCERTFP,
|
RPL_WHOISCERTFP,
|
||||||
RPL_ACCEPTLIST,
|
RPL_ACCEPTLIST,
|
||||||
RPL_ENDOFACCEPT,
|
RPL_ENDOFACCEPT,
|
||||||
RPL_NONE,
|
|
||||||
RPL_AWAY,
|
RPL_AWAY,
|
||||||
RPL_USERHOST,
|
RPL_USERHOST,
|
||||||
RPL_ISON,
|
RPL_ISON,
|
||||||
RPL_TEXT,
|
|
||||||
RPL_UNAWAY,
|
RPL_UNAWAY,
|
||||||
RPL_NOWAWAY,
|
RPL_NOWAWAY,
|
||||||
RPL_WHOISHELPOP,
|
RPL_WHOISHELPOP,
|
||||||
@@ -80,7 +71,6 @@ enum class IrcCommand
|
|||||||
RPL_WHOISOPERATOR,
|
RPL_WHOISOPERATOR,
|
||||||
RPL_WHOWASUSER,
|
RPL_WHOWASUSER,
|
||||||
RPL_ENDOFWHOWAS,
|
RPL_ENDOFWHOWAS,
|
||||||
RPL_WHOISCHANOP,
|
|
||||||
RPL_WHOISIDLE,
|
RPL_WHOISIDLE,
|
||||||
RPL_ENDOFWHOIS,
|
RPL_ENDOFWHOIS,
|
||||||
RPL_WHOISCHANNELS,
|
RPL_WHOISCHANNELS,
|
||||||
@@ -96,10 +86,8 @@ enum class IrcCommand
|
|||||||
RPL_NOTOPIC,
|
RPL_NOTOPIC,
|
||||||
RPL_TOPIC,
|
RPL_TOPIC,
|
||||||
RPL_TOPICWHOTIME,
|
RPL_TOPICWHOTIME,
|
||||||
RPL_WHOISTEXT,
|
|
||||||
RPL_WHOISACTUALLY,
|
RPL_WHOISACTUALLY,
|
||||||
RPL_INVITING,
|
RPL_INVITING,
|
||||||
RPL_SUMMONING,
|
|
||||||
RPL_INVITELIST,
|
RPL_INVITELIST,
|
||||||
RPL_ENDOFINVITELIST,
|
RPL_ENDOFINVITELIST,
|
||||||
RPL_EXCEPTLIST,
|
RPL_EXCEPTLIST,
|
||||||
@@ -111,7 +99,6 @@ enum class IrcCommand
|
|||||||
RPL_NAMREPLY,
|
RPL_NAMREPLY,
|
||||||
RPL_WHOWASREAL,
|
RPL_WHOWASREAL,
|
||||||
RPL_ENDOFNAMES,
|
RPL_ENDOFNAMES,
|
||||||
RPL_KILLDONE,
|
|
||||||
RPL_CLOSING,
|
RPL_CLOSING,
|
||||||
RPL_CLOSEEND,
|
RPL_CLOSEEND,
|
||||||
RPL_LINKS,
|
RPL_LINKS,
|
||||||
@@ -120,21 +107,14 @@ enum class IrcCommand
|
|||||||
RPL_ENDOFBANLIST,
|
RPL_ENDOFBANLIST,
|
||||||
RPL_INFO,
|
RPL_INFO,
|
||||||
RPL_MOTD,
|
RPL_MOTD,
|
||||||
RPL_INFOSTART,
|
|
||||||
RPL_ENDOFINFO,
|
RPL_ENDOFINFO,
|
||||||
RPL_MOTDSTART,
|
RPL_MOTDSTART,
|
||||||
RPL_ENDOFMOTD,
|
RPL_ENDOFMOTD,
|
||||||
RPL_WHOISHOST,
|
RPL_WHOISHOST,
|
||||||
RPL_YOUREOPER,
|
RPL_YOUREOPER,
|
||||||
RPL_REHASHING,
|
RPL_REHASHING,
|
||||||
RPL_MYPORTIS,
|
|
||||||
RPL_NOTOPERANYMORE,
|
|
||||||
RPL_RSACHALLENGE,
|
RPL_RSACHALLENGE,
|
||||||
RPL_TIME,
|
RPL_TIME,
|
||||||
RPL_USERSSTART,
|
|
||||||
RPL_USERS,
|
|
||||||
RPL_ENDOFUSERS,
|
|
||||||
RPL_NOUSERS,
|
|
||||||
RPL_HOSTHIDDEN,
|
RPL_HOSTHIDDEN,
|
||||||
ERR_NOSUCHNICK,
|
ERR_NOSUCHNICK,
|
||||||
ERR_NOSUCHSERVER,
|
ERR_NOSUCHSERVER,
|
||||||
@@ -153,8 +133,6 @@ enum class IrcCommand
|
|||||||
ERR_TOOMANYMATCHES,
|
ERR_TOOMANYMATCHES,
|
||||||
ERR_UNKNOWNCOMMAND,
|
ERR_UNKNOWNCOMMAND,
|
||||||
ERR_NOMOTD,
|
ERR_NOMOTD,
|
||||||
ERR_NOADMININFO,
|
|
||||||
ERR_FILEERROR,
|
|
||||||
ERR_NONICKNAMEGIVEN,
|
ERR_NONICKNAMEGIVEN,
|
||||||
ERR_ERRONEUSNICKNAME,
|
ERR_ERRONEUSNICKNAME,
|
||||||
ERR_NICKNAMEINUSE,
|
ERR_NICKNAMEINUSE,
|
||||||
@@ -166,27 +144,20 @@ enum class IrcCommand
|
|||||||
ERR_USERNOTINCHANNEL,
|
ERR_USERNOTINCHANNEL,
|
||||||
ERR_NOTONCHANNEL,
|
ERR_NOTONCHANNEL,
|
||||||
ERR_USERONCHANNEL,
|
ERR_USERONCHANNEL,
|
||||||
ERR_NOLOGIN,
|
|
||||||
ERR_SUMMONDISABLED,
|
|
||||||
ERR_USERSDISABLED,
|
|
||||||
ERR_NOTREGISTERED,
|
ERR_NOTREGISTERED,
|
||||||
ERR_ACCEPTFULL,
|
ERR_ACCEPTFULL,
|
||||||
ERR_ACCEPTEXIST,
|
ERR_ACCEPTEXIST,
|
||||||
ERR_ACCEPTNOT,
|
ERR_ACCEPTNOT,
|
||||||
ERR_NEEDMOREPARAMS,
|
ERR_NEEDMOREPARAMS,
|
||||||
ERR_ALREADYREGISTRED,
|
ERR_ALREADYREGISTRED,
|
||||||
ERR_NOPERMFORHOST,
|
|
||||||
ERR_PASSWDMISMATCH,
|
ERR_PASSWDMISMATCH,
|
||||||
ERR_YOUREBANNEDCREEP,
|
ERR_YOUREBANNEDCREEP,
|
||||||
ERR_YOUWILLBEBANNED,
|
|
||||||
ERR_KEYSET,
|
|
||||||
ERR_LINKCHANNEL,
|
ERR_LINKCHANNEL,
|
||||||
ERR_CHANNELISFULL,
|
ERR_CHANNELISFULL,
|
||||||
ERR_UNKNOWNMODE,
|
ERR_UNKNOWNMODE,
|
||||||
ERR_INVITEONLYCHAN,
|
ERR_INVITEONLYCHAN,
|
||||||
ERR_BANNEDFROMCHAN,
|
ERR_BANNEDFROMCHAN,
|
||||||
ERR_BADCHANNELKEY,
|
ERR_BADCHANNELKEY,
|
||||||
ERR_BADCHANMASK,
|
|
||||||
ERR_NEEDREGGEDNICK,
|
ERR_NEEDREGGEDNICK,
|
||||||
ERR_BANLISTFULL,
|
ERR_BANLISTFULL,
|
||||||
ERR_BADCHANNAME,
|
ERR_BADCHANNAME,
|
||||||
@@ -195,7 +166,6 @@ enum class IrcCommand
|
|||||||
ERR_CHANOPRIVSNEEDED,
|
ERR_CHANOPRIVSNEEDED,
|
||||||
ERR_CANTKILLSERVER,
|
ERR_CANTKILLSERVER,
|
||||||
ERR_ISCHANSERVICE,
|
ERR_ISCHANSERVICE,
|
||||||
ERR_BANNEDNICK,
|
|
||||||
ERR_NONONREG,
|
ERR_NONONREG,
|
||||||
ERR_VOICENEEDED,
|
ERR_VOICENEEDED,
|
||||||
ERR_NOOPERHOST,
|
ERR_NOOPERHOST,
|
||||||
@@ -203,7 +173,6 @@ enum class IrcCommand
|
|||||||
ERR_OWNMODE,
|
ERR_OWNMODE,
|
||||||
ERR_UMODEUNKNOWNFLAG,
|
ERR_UMODEUNKNOWNFLAG,
|
||||||
ERR_USERSDONTMATCH,
|
ERR_USERSDONTMATCH,
|
||||||
ERR_GHOSTEDCLIENT,
|
|
||||||
ERR_USERNOTONSERV,
|
ERR_USERNOTONSERV,
|
||||||
ERR_WRONGPONG,
|
ERR_WRONGPONG,
|
||||||
ERR_DISABLED,
|
ERR_DISABLED,
|
||||||
@@ -232,10 +201,9 @@ enum class IrcCommand
|
|||||||
RPL_OMOTD,
|
RPL_OMOTD,
|
||||||
RPL_ENDOFOMOTD,
|
RPL_ENDOFOMOTD,
|
||||||
ERR_NOPRIVS,
|
ERR_NOPRIVS,
|
||||||
RPL_TESTMASK,
|
|
||||||
RPL_TESTLINE,
|
RPL_TESTLINE,
|
||||||
RPL_NOTESTLINE,
|
RPL_NOTESTLINE,
|
||||||
RPL_TESTMASKGECO,
|
RPL_TESTMASKGECOS,
|
||||||
RPL_QUIETLIST,
|
RPL_QUIETLIST,
|
||||||
RPL_ENDOFQUIETLIS,
|
RPL_ENDOFQUIETLIS,
|
||||||
RPL_MONONLINE,
|
RPL_MONONLINE,
|
||||||
@@ -247,7 +215,6 @@ enum class IrcCommand
|
|||||||
RPL_ENDOFRSACHALLENGE2,
|
RPL_ENDOFRSACHALLENGE2,
|
||||||
ERR_MLOCKRESTRICTE,
|
ERR_MLOCKRESTRICTE,
|
||||||
ERR_INVALIDBAN,
|
ERR_INVALIDBAN,
|
||||||
ERR_TOPICLOCK,
|
|
||||||
RPL_SCANMATCHED,
|
RPL_SCANMATCHED,
|
||||||
RPL_SCANUMODES,
|
RPL_SCANUMODES,
|
||||||
RPL_LOGGEDIN,
|
RPL_LOGGEDIN,
|
||||||
|
@@ -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]() {
|
disconnect_slot_ = get_connection().sig_disconnect.connect([this](auto) {
|
||||||
handle_.resume();
|
handle_.resume();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -12,8 +12,6 @@
|
|||||||
|
|
||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <concepts>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace myirc {
|
namespace myirc {
|
||||||
@@ -24,11 +22,13 @@ namespace myirc {
|
|||||||
*/
|
*/
|
||||||
class LineBuffer
|
class LineBuffer
|
||||||
{
|
{
|
||||||
std::vector<char> buffer;
|
std::vector<char> buffer_;
|
||||||
|
|
||||||
// [buffer.begin(), end_) contains buffered data
|
// [std::begin(buffer), end_) contains buffered data
|
||||||
// [end_, buffer.end()) is available buffer space
|
// [end_, std::end(buffer)) is available buffer space
|
||||||
std::vector<char>::iterator end_;
|
decltype(buffer_)::iterator start_;
|
||||||
|
decltype(buffer_)::iterator search_;
|
||||||
|
decltype(buffer_)::iterator end_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -37,19 +37,27 @@ public:
|
|||||||
* @param n Buffer size
|
* @param n Buffer size
|
||||||
*/
|
*/
|
||||||
LineBuffer(std::size_t n)
|
LineBuffer(std::size_t n)
|
||||||
: buffer(n)
|
: buffer_(n)
|
||||||
, end_{buffer.begin()}
|
, start_{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 get_buffer() -> boost::asio::mutable_buffer
|
auto prepare() -> boost::asio::mutable_buffer
|
||||||
{
|
{
|
||||||
return boost::asio::buffer(&*end_, std::distance(end_, buffer.end()));
|
return boost::asio::buffer(&*end_, std::distance(end_, buffer_.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,48 +66,39 @@ 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 get_buffer and
|
* and the is ready for additional calls to prepare and
|
||||||
* add_bytes.
|
* commit.
|
||||||
*
|
*
|
||||||
* @param n Bytes written to the last call of get_buffer
|
* @param n Bytes written to the last call of prepare
|
||||||
* @param line_cb Callback function to run on each completed line
|
* @param line_cb Callback function to run on each completed line
|
||||||
*/
|
*/
|
||||||
auto add_bytes(std::size_t n, std::invocable<char *> auto line_cb) -> void
|
auto commit(std::size_t const n) -> 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);
|
/**
|
||||||
|
* @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*;
|
||||||
|
|
||||||
cursor = std::next(nl);
|
/**
|
||||||
}
|
* @brief Return the next non-empty line if there is one.
|
||||||
|
*/
|
||||||
|
auto next_nonempty_line() -> char*;
|
||||||
|
|
||||||
// If any lines were processed, move all processed lines to
|
/**
|
||||||
// the front of the buffer
|
* @brief Reclaim used buffer space invalidating all previous
|
||||||
if (cursor != buffer.begin())
|
* next_line() results;
|
||||||
{
|
*
|
||||||
end_ = std::move(cursor, end_, buffer.begin());
|
*/
|
||||||
}
|
auto shift() -> void;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace myirc
|
} // namespace
|
||||||
|
@@ -42,6 +42,12 @@ 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) {
|
||||||
|
@@ -24,10 +24,9 @@ struct RecognizedCommand {
|
|||||||
206, IrcCommand::RPL_TRACESERVER, 8, 8
|
206, IrcCommand::RPL_TRACESERVER, 8, 8
|
||||||
208, IrcCommand::RPL_TRACENEWTYPE, 4, 4
|
208, IrcCommand::RPL_TRACENEWTYPE, 4, 4
|
||||||
209, IrcCommand::RPL_TRACECLASS, 4, 4
|
209, IrcCommand::RPL_TRACECLASS, 4, 4
|
||||||
211, IrcCommand::RPL_STATSLINKINFO
|
211, IrcCommand::RPL_STATSLINKINFO, 8, 8
|
||||||
212, IrcCommand::RPL_STATSCOMMANDS, 5, 5
|
212, IrcCommand::RPL_STATSCOMMANDS, 5, 5
|
||||||
213, IrcCommand::RPL_STATSCLINE, 8, 8
|
213, IrcCommand::RPL_STATSCLINE, 8, 8
|
||||||
214, IrcCommand::RPL_STATSNLINE
|
|
||||||
215, IrcCommand::RPL_STATSILINE, 8, 8
|
215, IrcCommand::RPL_STATSILINE, 8, 8
|
||||||
216, IrcCommand::RPL_STATSKLINE, 6, 6
|
216, IrcCommand::RPL_STATSKLINE, 6, 6
|
||||||
217, IrcCommand::RPL_STATSQLINE, 5, 5
|
217, IrcCommand::RPL_STATSQLINE, 5, 5
|
||||||
@@ -35,15 +34,11 @@ struct RecognizedCommand {
|
|||||||
219, IrcCommand::RPL_ENDOFSTATS, 3, 3
|
219, IrcCommand::RPL_ENDOFSTATS, 3, 3
|
||||||
220, IrcCommand::RPL_STATSPLINE, 6, 6
|
220, IrcCommand::RPL_STATSPLINE, 6, 6
|
||||||
221, IrcCommand::RPL_UMODEIS, 2, 2
|
221, IrcCommand::RPL_UMODEIS, 2, 2
|
||||||
224, IrcCommand::RPL_STATSFLINE
|
|
||||||
225, IrcCommand::RPL_STATSDLINE, 4, 4
|
225, IrcCommand::RPL_STATSDLINE, 4, 4
|
||||||
234, IrcCommand::RPL_SERVLIST
|
|
||||||
235, IrcCommand::RPL_SERVLISTEND
|
|
||||||
241, IrcCommand::RPL_STATSLLINE, 7, 7
|
241, IrcCommand::RPL_STATSLLINE, 7, 7
|
||||||
242, IrcCommand::RPL_STATSUPTIME, 2, 2
|
242, IrcCommand::RPL_STATSUPTIME, 2, 2
|
||||||
243, IrcCommand::RPL_STATSOLINE, 7, 7
|
243, IrcCommand::RPL_STATSOLINE, 7, 7
|
||||||
244, IrcCommand::RPL_STATSHLINE, 7, 7
|
244, IrcCommand::RPL_STATSHLINE, 7, 7
|
||||||
245, IrcCommand::RPL_STATSSLINE
|
|
||||||
247, IrcCommand::RPL_STATSXLINE, 5, 5
|
247, IrcCommand::RPL_STATSXLINE, 5, 5
|
||||||
248, IrcCommand::RPL_STATSULINE, 5, 5
|
248, IrcCommand::RPL_STATSULINE, 5, 5
|
||||||
249, IrcCommand::RPL_STATSDEBUG, 3, 3
|
249, IrcCommand::RPL_STATSDEBUG, 3, 3
|
||||||
@@ -57,7 +52,6 @@ struct RecognizedCommand {
|
|||||||
257, IrcCommand::RPL_ADMINLOC1, 2, 2
|
257, IrcCommand::RPL_ADMINLOC1, 2, 2
|
||||||
258, IrcCommand::RPL_ADMINLOC2, 2, 2
|
258, IrcCommand::RPL_ADMINLOC2, 2, 2
|
||||||
259, IrcCommand::RPL_ADMINEMAIL, 2, 2
|
259, IrcCommand::RPL_ADMINEMAIL, 2, 2
|
||||||
261, IrcCommand::RPL_TRACELOG
|
|
||||||
262, IrcCommand::RPL_ENDOFTRACE, 3, 3
|
262, IrcCommand::RPL_ENDOFTRACE, 3, 3
|
||||||
263, IrcCommand::RPL_LOAD2HI, 3, 3
|
263, IrcCommand::RPL_LOAD2HI, 3, 3
|
||||||
265, IrcCommand::RPL_LOCALUSERS, 4, 4
|
265, IrcCommand::RPL_LOCALUSERS, 4, 4
|
||||||
@@ -66,11 +60,9 @@ struct RecognizedCommand {
|
|||||||
276, IrcCommand::RPL_WHOISCERTFP, 3, 3
|
276, IrcCommand::RPL_WHOISCERTFP, 3, 3
|
||||||
281, IrcCommand::RPL_ACCEPTLIST, 1, 15
|
281, IrcCommand::RPL_ACCEPTLIST, 1, 15
|
||||||
282, IrcCommand::RPL_ENDOFACCEPT, 2, 2
|
282, IrcCommand::RPL_ENDOFACCEPT, 2, 2
|
||||||
300, IrcCommand::RPL_NONE
|
|
||||||
301, IrcCommand::RPL_AWAY, 3, 3
|
301, IrcCommand::RPL_AWAY, 3, 3
|
||||||
302, IrcCommand::RPL_USERHOST, 2, 2
|
302, IrcCommand::RPL_USERHOST, 2, 2
|
||||||
303, IrcCommand::RPL_ISON, 2, 2
|
303, IrcCommand::RPL_ISON, 2, 2
|
||||||
304, IrcCommand::RPL_TEXT
|
|
||||||
305, IrcCommand::RPL_UNAWAY, 2, 2
|
305, IrcCommand::RPL_UNAWAY, 2, 2
|
||||||
306, IrcCommand::RPL_NOWAWAY, 2, 2
|
306, IrcCommand::RPL_NOWAWAY, 2, 2
|
||||||
310, IrcCommand::RPL_WHOISHELPOP, 3, 3
|
310, IrcCommand::RPL_WHOISHELPOP, 3, 3
|
||||||
@@ -79,7 +71,6 @@ struct RecognizedCommand {
|
|||||||
313, IrcCommand::RPL_WHOISOPERATOR, 3, 3
|
313, IrcCommand::RPL_WHOISOPERATOR, 3, 3
|
||||||
314, IrcCommand::RPL_WHOWASUSER, 6, 6
|
314, IrcCommand::RPL_WHOWASUSER, 6, 6
|
||||||
369, IrcCommand::RPL_ENDOFWHOWAS, 3, 3
|
369, IrcCommand::RPL_ENDOFWHOWAS, 3, 3
|
||||||
316, IrcCommand::RPL_WHOISCHANOP
|
|
||||||
317, IrcCommand::RPL_WHOISIDLE, 5, 5
|
317, IrcCommand::RPL_WHOISIDLE, 5, 5
|
||||||
318, IrcCommand::RPL_ENDOFWHOIS, 3, 3
|
318, IrcCommand::RPL_ENDOFWHOIS, 3, 3
|
||||||
319, IrcCommand::RPL_WHOISCHANNELS, 3, 3
|
319, IrcCommand::RPL_WHOISCHANNELS, 3, 3
|
||||||
@@ -89,7 +80,7 @@ struct RecognizedCommand {
|
|||||||
323, IrcCommand::RPL_LISTEND, 2, 2
|
323, IrcCommand::RPL_LISTEND, 2, 2
|
||||||
324, IrcCommand::RPL_CHANNELMODEIS, 3, 3
|
324, IrcCommand::RPL_CHANNELMODEIS, 3, 3
|
||||||
325, IrcCommand::RPL_CHANNELMLOCK, 4, 4
|
325, IrcCommand::RPL_CHANNELMLOCK, 4, 4
|
||||||
328, IrcCommand::RPL_CHANNELURL
|
328, IrcCommand::RPL_CHANNELURL, 2, 2
|
||||||
329, IrcCommand::RPL_CREATIONTIME, 3, 3
|
329, IrcCommand::RPL_CREATIONTIME, 3, 3
|
||||||
330, IrcCommand::RPL_WHOISLOGGEDIN, 4, 4
|
330, IrcCommand::RPL_WHOISLOGGEDIN, 4, 4
|
||||||
331, IrcCommand::RPL_NOTOPIC, 3, 3
|
331, IrcCommand::RPL_NOTOPIC, 3, 3
|
||||||
@@ -97,19 +88,17 @@ struct RecognizedCommand {
|
|||||||
333, IrcCommand::RPL_TOPICWHOTIME, 4, 4
|
333, IrcCommand::RPL_TOPICWHOTIME, 4, 4
|
||||||
338, IrcCommand::RPL_WHOISACTUALLY, 4, 4
|
338, IrcCommand::RPL_WHOISACTUALLY, 4, 4
|
||||||
341, IrcCommand::RPL_INVITING, 3, 3
|
341, IrcCommand::RPL_INVITING, 3, 3
|
||||||
342, IrcCommand::RPL_SUMMONING
|
|
||||||
346, IrcCommand::RPL_INVITELIST, 5, 5
|
346, IrcCommand::RPL_INVITELIST, 5, 5
|
||||||
347, IrcCommand::RPL_ENDOFINVITELIST, 3, 3
|
347, IrcCommand::RPL_ENDOFINVITELIST, 3, 3
|
||||||
348, IrcCommand::RPL_EXCEPTLIST, 5, 5
|
348, IrcCommand::RPL_EXCEPTLIST, 5, 5
|
||||||
349, IrcCommand::RPL_ENDOFEXCEPTLIST, 3, 3
|
349, IrcCommand::RPL_ENDOFEXCEPTLIST, 3, 3
|
||||||
351, IrcCommand::RPL_VERSION, 4, 4
|
351, IrcCommand::RPL_VERSION, 4, 4
|
||||||
352, IrcCommand::RPL_WHOREPLY, 8, 8
|
352, IrcCommand::RPL_WHOREPLY, 8, 8
|
||||||
354, IrcCommand::RPL_WHOSPCRPL
|
354, IrcCommand::RPL_WHOSPCRPL, 1, 15
|
||||||
315, IrcCommand::RPL_ENDOFWHO, 3, 3
|
315, IrcCommand::RPL_ENDOFWHO, 3, 3
|
||||||
353, IrcCommand::RPL_NAMREPLY, 4, 4
|
353, IrcCommand::RPL_NAMREPLY, 4, 4
|
||||||
360, IrcCommand::RPL_WHOWASREAL, 3, 3
|
360, IrcCommand::RPL_WHOWASREAL, 3, 3
|
||||||
366, IrcCommand::RPL_ENDOFNAMES, 3, 3
|
366, IrcCommand::RPL_ENDOFNAMES, 3, 3
|
||||||
361, IrcCommand::RPL_KILLDONE
|
|
||||||
362, IrcCommand::RPL_CLOSING, 3, 3
|
362, IrcCommand::RPL_CLOSING, 3, 3
|
||||||
363, IrcCommand::RPL_CLOSEEND, 3, 3
|
363, IrcCommand::RPL_CLOSEEND, 3, 3
|
||||||
364, IrcCommand::RPL_LINKS, 4, 4
|
364, IrcCommand::RPL_LINKS, 4, 4
|
||||||
@@ -118,22 +107,15 @@ struct RecognizedCommand {
|
|||||||
368, IrcCommand::RPL_ENDOFBANLIST, 3, 3
|
368, IrcCommand::RPL_ENDOFBANLIST, 3, 3
|
||||||
371, IrcCommand::RPL_INFO, 2, 2
|
371, IrcCommand::RPL_INFO, 2, 2
|
||||||
372, IrcCommand::RPL_MOTD, 2, 2
|
372, IrcCommand::RPL_MOTD, 2, 2
|
||||||
373, IrcCommand::RPL_INFOSTART
|
|
||||||
374, IrcCommand::RPL_ENDOFINFO, 2, 2
|
374, IrcCommand::RPL_ENDOFINFO, 2, 2
|
||||||
375, IrcCommand::RPL_MOTDSTART, 2, 2
|
375, IrcCommand::RPL_MOTDSTART, 2, 2
|
||||||
376, IrcCommand::RPL_ENDOFMOTD, 2, 2
|
376, IrcCommand::RPL_ENDOFMOTD, 2, 2
|
||||||
378, IrcCommand::RPL_WHOISHOST, 3, 3
|
378, IrcCommand::RPL_WHOISHOST, 3, 3
|
||||||
381, IrcCommand::RPL_YOUREOPER, 2, 2
|
381, IrcCommand::RPL_YOUREOPER, 2, 2
|
||||||
382, IrcCommand::RPL_REHASHING, 3, 3
|
382, IrcCommand::RPL_REHASHING, 3, 3
|
||||||
384, IrcCommand::RPL_MYPORTIS
|
|
||||||
385, IrcCommand::RPL_NOTOPERANYMORE
|
|
||||||
386, IrcCommand::RPL_RSACHALLENGE, 2, 2
|
386, IrcCommand::RPL_RSACHALLENGE, 2, 2
|
||||||
391, IrcCommand::RPL_TIME, 3, 3
|
391, IrcCommand::RPL_TIME, 3, 3
|
||||||
392, IrcCommand::RPL_USERSSTART
|
396, IrcCommand::RPL_HOSTHIDDEN, 2, 2
|
||||||
393, IrcCommand::RPL_USERS
|
|
||||||
394, IrcCommand::RPL_ENDOFUSERS
|
|
||||||
395, IrcCommand::RPL_NOUSERS
|
|
||||||
396, IrcCommand::RPL_HOSTHIDDEN
|
|
||||||
401, IrcCommand::ERR_NOSUCHNICK, 3, 3
|
401, IrcCommand::ERR_NOSUCHNICK, 3, 3
|
||||||
402, IrcCommand::ERR_NOSUCHSERVER, 3, 3
|
402, IrcCommand::ERR_NOSUCHSERVER, 3, 3
|
||||||
403, IrcCommand::ERR_NOSUCHCHANNEL, 3, 3
|
403, IrcCommand::ERR_NOSUCHCHANNEL, 3, 3
|
||||||
@@ -151,8 +133,6 @@ struct RecognizedCommand {
|
|||||||
416, IrcCommand::ERR_TOOMANYMATCHES, 3, 3
|
416, IrcCommand::ERR_TOOMANYMATCHES, 3, 3
|
||||||
421, IrcCommand::ERR_UNKNOWNCOMMAND, 3, 3
|
421, IrcCommand::ERR_UNKNOWNCOMMAND, 3, 3
|
||||||
422, IrcCommand::ERR_NOMOTD, 2, 2
|
422, IrcCommand::ERR_NOMOTD, 2, 2
|
||||||
423, IrcCommand::ERR_NOADMININFO
|
|
||||||
424, IrcCommand::ERR_FILEERROR
|
|
||||||
431, IrcCommand::ERR_NONICKNAMEGIVEN, 2, 2
|
431, IrcCommand::ERR_NONICKNAMEGIVEN, 2, 2
|
||||||
432, IrcCommand::ERR_ERRONEUSNICKNAME, 3, 3
|
432, IrcCommand::ERR_ERRONEUSNICKNAME, 3, 3
|
||||||
433, IrcCommand::ERR_NICKNAMEINUSE, 3, 3
|
433, IrcCommand::ERR_NICKNAMEINUSE, 3, 3
|
||||||
@@ -164,27 +144,20 @@ struct RecognizedCommand {
|
|||||||
441, IrcCommand::ERR_USERNOTINCHANNEL, 4, 4
|
441, IrcCommand::ERR_USERNOTINCHANNEL, 4, 4
|
||||||
442, IrcCommand::ERR_NOTONCHANNEL, 3, 3
|
442, IrcCommand::ERR_NOTONCHANNEL, 3, 3
|
||||||
443, IrcCommand::ERR_USERONCHANNEL, 4, 4
|
443, IrcCommand::ERR_USERONCHANNEL, 4, 4
|
||||||
444, IrcCommand::ERR_NOLOGIN
|
|
||||||
445, IrcCommand::ERR_SUMMONDISABLED
|
|
||||||
446, IrcCommand::ERR_USERSDISABLED
|
|
||||||
451, IrcCommand::ERR_NOTREGISTERED, 2, 2
|
451, IrcCommand::ERR_NOTREGISTERED, 2, 2
|
||||||
456, IrcCommand::ERR_ACCEPTFULL, 2, 2
|
456, IrcCommand::ERR_ACCEPTFULL, 2, 2
|
||||||
457, IrcCommand::ERR_ACCEPTEXIST, 3, 3
|
457, IrcCommand::ERR_ACCEPTEXIST, 3, 3
|
||||||
458, IrcCommand::ERR_ACCEPTNOT, 3, 3
|
458, IrcCommand::ERR_ACCEPTNOT, 3, 3
|
||||||
461, IrcCommand::ERR_NEEDMOREPARAMS, 3, 3
|
461, IrcCommand::ERR_NEEDMOREPARAMS, 3, 3
|
||||||
462, IrcCommand::ERR_ALREADYREGISTRED, 2, 2
|
462, IrcCommand::ERR_ALREADYREGISTRED, 2, 2
|
||||||
463, IrcCommand::ERR_NOPERMFORHOST
|
|
||||||
464, IrcCommand::ERR_PASSWDMISMATCH, 2, 2
|
464, IrcCommand::ERR_PASSWDMISMATCH, 2, 2
|
||||||
465, IrcCommand::ERR_YOUREBANNEDCREEP, 2, 2
|
465, IrcCommand::ERR_YOUREBANNEDCREEP, 2, 2
|
||||||
466, IrcCommand::ERR_YOUWILLBEBANNED
|
|
||||||
467, IrcCommand::ERR_KEYSET
|
|
||||||
470, IrcCommand::ERR_LINKCHANNEL, 4, 4
|
470, IrcCommand::ERR_LINKCHANNEL, 4, 4
|
||||||
471, IrcCommand::ERR_CHANNELISFULL, 3, 3
|
471, IrcCommand::ERR_CHANNELISFULL, 3, 3
|
||||||
472, IrcCommand::ERR_UNKNOWNMODE, 3, 3
|
472, IrcCommand::ERR_UNKNOWNMODE, 3, 3
|
||||||
473, IrcCommand::ERR_INVITEONLYCHAN, 3, 3
|
473, IrcCommand::ERR_INVITEONLYCHAN, 3, 3
|
||||||
474, IrcCommand::ERR_BANNEDFROMCHAN, 3, 3
|
474, IrcCommand::ERR_BANNEDFROMCHAN, 3, 3
|
||||||
475, IrcCommand::ERR_BADCHANNELKEY, 3, 3
|
475, IrcCommand::ERR_BADCHANNELKEY, 3, 3
|
||||||
476, IrcCommand::ERR_BADCHANMASK
|
|
||||||
477, IrcCommand::ERR_NEEDREGGEDNICK, 3, 3
|
477, IrcCommand::ERR_NEEDREGGEDNICK, 3, 3
|
||||||
478, IrcCommand::ERR_BANLISTFULL, 4, 4
|
478, IrcCommand::ERR_BANLISTFULL, 4, 4
|
||||||
479, IrcCommand::ERR_BADCHANNAME, 3, 3
|
479, IrcCommand::ERR_BADCHANNAME, 3, 3
|
||||||
@@ -193,7 +166,6 @@ struct RecognizedCommand {
|
|||||||
482, IrcCommand::ERR_CHANOPRIVSNEEDED, 3, 3
|
482, IrcCommand::ERR_CHANOPRIVSNEEDED, 3, 3
|
||||||
483, IrcCommand::ERR_CANTKILLSERVER, 2, 2
|
483, IrcCommand::ERR_CANTKILLSERVER, 2, 2
|
||||||
484, IrcCommand::ERR_ISCHANSERVICE, 4, 4
|
484, IrcCommand::ERR_ISCHANSERVICE, 4, 4
|
||||||
485, IrcCommand::ERR_BANNEDNICK
|
|
||||||
486, IrcCommand::ERR_NONONREG, 3, 3
|
486, IrcCommand::ERR_NONONREG, 3, 3
|
||||||
489, IrcCommand::ERR_VOICENEEDED, 3, 3
|
489, IrcCommand::ERR_VOICENEEDED, 3, 3
|
||||||
491, IrcCommand::ERR_NOOPERHOST, 2, 2
|
491, IrcCommand::ERR_NOOPERHOST, 2, 2
|
||||||
@@ -201,7 +173,6 @@ struct RecognizedCommand {
|
|||||||
494, IrcCommand::ERR_OWNMODE, 3, 3
|
494, IrcCommand::ERR_OWNMODE, 3, 3
|
||||||
501, IrcCommand::ERR_UMODEUNKNOWNFLAG, 2, 2
|
501, IrcCommand::ERR_UMODEUNKNOWNFLAG, 2, 2
|
||||||
502, IrcCommand::ERR_USERSDONTMATCH, 2, 2
|
502, IrcCommand::ERR_USERSDONTMATCH, 2, 2
|
||||||
503, IrcCommand::ERR_GHOSTEDCLIENT
|
|
||||||
504, IrcCommand::ERR_USERNOTONSERV, 3, 3
|
504, IrcCommand::ERR_USERNOTONSERV, 3, 3
|
||||||
513, IrcCommand::ERR_WRONGPONG, 2, 2
|
513, IrcCommand::ERR_WRONGPONG, 2, 2
|
||||||
517, IrcCommand::ERR_DISABLED, 3, 3
|
517, IrcCommand::ERR_DISABLED, 3, 3
|
||||||
@@ -224,16 +195,15 @@ struct RecognizedCommand {
|
|||||||
714, IrcCommand::ERR_KNOCKONCHAN, 3, 3
|
714, IrcCommand::ERR_KNOCKONCHAN, 3, 3
|
||||||
715, IrcCommand::ERR_KNOCKDISABLED, 2, 2
|
715, IrcCommand::ERR_KNOCKDISABLED, 2, 2
|
||||||
716, IrcCommand::ERR_TARGUMODEG, 3, 3
|
716, IrcCommand::ERR_TARGUMODEG, 3, 3
|
||||||
717, IrcCommand::RPL_TARGNOTIFY
|
717, IrcCommand::RPL_TARGNOTIFY, 3, 3
|
||||||
718, IrcCommand::RPL_UMODEGMSG, 4, 4
|
718, IrcCommand::RPL_UMODEGMSG, 4, 4
|
||||||
720, IrcCommand::RPL_OMOTDSTART, 2, 2
|
720, IrcCommand::RPL_OMOTDSTART, 2, 2
|
||||||
721, IrcCommand::RPL_OMOTD, 2, 2
|
721, IrcCommand::RPL_OMOTD, 2, 2
|
||||||
722, IrcCommand::RPL_ENDOFOMOTD, 2, 2
|
722, IrcCommand::RPL_ENDOFOMOTD, 2, 2
|
||||||
723, IrcCommand::ERR_NOPRIVS, 3, 3
|
723, IrcCommand::ERR_NOPRIVS, 3, 3
|
||||||
724, IrcCommand::RPL_TESTMASK
|
|
||||||
725, IrcCommand::RPL_TESTLINE, 5, 5
|
725, IrcCommand::RPL_TESTLINE, 5, 5
|
||||||
726, IrcCommand::RPL_NOTESTLINE
|
726, IrcCommand::RPL_NOTESTLINE, 3, 3
|
||||||
727, IrcCommand::RPL_TESTMASKGECO, 6, 6
|
727, IrcCommand::RPL_TESTMASKGECOS, 6, 6
|
||||||
728, IrcCommand::RPL_QUIETLIST, 6, 6
|
728, IrcCommand::RPL_QUIETLIST, 6, 6
|
||||||
729, IrcCommand::RPL_ENDOFQUIETLIS, 4, 4
|
729, IrcCommand::RPL_ENDOFQUIETLIS, 4, 4
|
||||||
730, IrcCommand::RPL_MONONLINE, 2, 2
|
730, IrcCommand::RPL_MONONLINE, 2, 2
|
||||||
@@ -245,7 +215,6 @@ struct RecognizedCommand {
|
|||||||
741, IrcCommand::RPL_ENDOFRSACHALLENGE2, 2, 2
|
741, IrcCommand::RPL_ENDOFRSACHALLENGE2, 2, 2
|
||||||
742, IrcCommand::ERR_MLOCKRESTRICTE, 5, 5
|
742, IrcCommand::ERR_MLOCKRESTRICTE, 5, 5
|
||||||
743, IrcCommand::ERR_INVALIDBAN, 5, 5
|
743, IrcCommand::ERR_INVALIDBAN, 5, 5
|
||||||
744, IrcCommand::ERR_TOPICLOCK
|
|
||||||
750, IrcCommand::RPL_SCANMATCHED, 3, 3
|
750, IrcCommand::RPL_SCANMATCHED, 3, 3
|
||||||
751, IrcCommand::RPL_SCANUMODES, 8, 8
|
751, IrcCommand::RPL_SCANUMODES, 8, 8
|
||||||
900, IrcCommand::RPL_LOGGEDIN, 4, 4
|
900, IrcCommand::RPL_LOGGEDIN, 4, 4
|
||||||
@@ -260,8 +229,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
|
BATCH, IrcCommand::BATCH, 1, 15
|
||||||
BOUNCER, IrcCommand::BOUNCER
|
BOUNCER, IrcCommand::BOUNCER, 1, 15
|
||||||
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
|
||||||
@@ -274,7 +243,7 @@ NICK, IrcCommand::NICK, 1, 1
|
|||||||
NOTICE, IrcCommand::NOTICE, 2, 2
|
NOTICE, IrcCommand::NOTICE, 2, 2
|
||||||
PART, IrcCommand::PART, 1, 2
|
PART, IrcCommand::PART, 1, 2
|
||||||
PING, IrcCommand::PING, 1, 1
|
PING, IrcCommand::PING, 1, 1
|
||||||
PONG, IrcCommand::PONG, 1, 2
|
PONG, IrcCommand::PONG, 2, 2
|
||||||
PRIVMSG, IrcCommand::PRIVMSG, 2, 2
|
PRIVMSG, IrcCommand::PRIVMSG, 2, 2
|
||||||
QUIT, IrcCommand::QUIT, 1, 1
|
QUIT, IrcCommand::QUIT, 1, 1
|
||||||
SETNAME, IrcCommand::SETNAME, 1, 1
|
SETNAME, IrcCommand::SETNAME, 1, 1
|
||||||
|
54
myirc/linebuffer.cpp
Normal file
54
myirc/linebuffer.cpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#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
|
@@ -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 (size < password.size()) { return -1; }
|
if (std::cmp_less(size, password.size())) { return -1; }
|
||||||
std::copy(password.begin(), password.end(), buf);
|
std::copy(password.begin(), password.end(), buf);
|
||||||
return password.size();
|
return static_cast<int>(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));
|
||||||
|
@@ -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)
|
[self = shared_from_this()](const auto cmd, auto &msg, auto)
|
||||||
{
|
{
|
||||||
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())
|
||||||
{
|
{
|
||||||
connection.send_pass(settings_.password);
|
client_->send_pass(settings_.password);
|
||||||
}
|
}
|
||||||
connection.send_user(settings_.username, settings_.realname);
|
client_->send_user(settings_.username, settings_.realname);
|
||||||
connection.send_nick(settings_.nickname);
|
client_->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_->get_connection().send_cap_req(request);
|
client_->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_->get_connection().send_cap_end();
|
client_->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]() {
|
thread->slot_ = thread->client_->get_connection().sig_connect.connect([thread](auto, auto, auto) {
|
||||||
thread->slot_.disconnect();
|
thread->slot_.disconnect();
|
||||||
thread->on_connect();
|
thread->on_connect();
|
||||||
});
|
});
|
||||||
@@ -106,8 +106,7 @@ auto Registration::start(
|
|||||||
|
|
||||||
auto Registration::randomize_nick() -> void
|
auto Registration::randomize_nick() -> void
|
||||||
{
|
{
|
||||||
std::string new_nick;
|
std::string new_nick = settings_.nickname.substr(0, 8);
|
||||||
new_nick += settings_.nickname.substr(0, 8);
|
|
||||||
|
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 gen{rd()};
|
std::mt19937 gen{rd()};
|
||||||
@@ -118,7 +117,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_->get_connection().send_nick(new_nick);
|
client_->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 +139,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_->get_connection().send_cap_end();
|
client_->send_cap_end();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -167,7 +167,7 @@ static auto setup_database() -> hs_database_t *
|
|||||||
expressions.reserve(n);
|
expressions.reserve(n);
|
||||||
ids.reserve(n);
|
ids.reserve(n);
|
||||||
|
|
||||||
for (std::size_t i = 0; i < n; i++)
|
for (unsigned 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(), expressions.size(), HS_MODE_BLOCK, platform, &db, &error))
|
switch (hs_compile_multi(expressions.data(), flags.data(), ids.data(), static_cast<unsigned>(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,7 +225,8 @@ 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.size(),
|
message.data(),
|
||||||
|
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
|
||||||
|
Reference in New Issue
Block a user