split up driver and library

This commit is contained in:
Eric Mertens 2025-01-30 09:28:28 -08:00
parent 5218ea0892
commit 281937e2c5
31 changed files with 176 additions and 105 deletions

View File

@ -26,38 +26,7 @@ FetchContent_Declare(
FetchContent_MakeAvailable(tomlplusplus)
FetchContent_MakeAvailable(Boost)
add_custom_command(
OUTPUT irc_commands.inc
COMMAND
gperf
-C -Z IrcCommandHash -K text -L C++ -t
--output-file irc_commands.inc
${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf
VERBATIM)
add_subdirectory(mybase64)
add_subdirectory(mysocks5)
add_executable(xbot
main.cpp
irc_commands.inc
bot.cpp
challenge.cpp
client.cpp
connection.cpp
ircmsg.cpp
openssl_utils.cpp
registration.cpp
sasl_mechanism.cpp
settings.cpp
snote.cpp
)
target_include_directories(xbot PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(xbot PRIVATE
OpenSSL::SSL
Boost::signals2 Boost::log Boost::asio
tomlplusplus_tomlplusplus
PkgConfig::LIBHS
mysocks5 mybase64)
add_subdirectory(myirc)
add_subdirectory(driver)

12
driver/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
add_executable(xbot
main.cpp
settings.cpp
)
target_link_libraries(xbot PRIVATE
myirc
OpenSSL::SSL
Boost::signals2 Boost::log Boost::asio
tomlplusplus_tomlplusplus
PkgConfig::LIBHS
mysocks5 mybase64)

View File

@ -36,7 +36,16 @@ static auto start(boost::asio::io_context &io, const Settings &settings) -> void
const auto connection = std::make_shared<Connection>(io);
const auto client = Client::start(*connection);
Registration::start(settings, client);
Registration::start({
.nickname = settings.nickname,
.realname = settings.realname,
.username = settings.username,
.password = settings.password,
.sasl_mechanism = settings.sasl_mechanism,
.sasl_authcid = settings.sasl_authcid,
.sasl_authzid = settings.sasl_authzid,
.sasl_password = settings.sasl_password,
}, client);
const auto bot = Bot::start(client);
@ -59,7 +68,7 @@ static auto start(boost::asio::io_context &io, const Settings &settings) -> void
}
);
bot->sig_command.connect([connection](const Command &cmd) {
bot->sig_command.connect([connection](auto &cmd) {
std::cout << "COMMAND " << cmd.command << " from " << cmd.account << std::endl;
if (cmd.oper == "glguy" && cmd.command == "ping") {
connection->send_notice("glguy", cmd.arguments);
@ -91,7 +100,8 @@ static auto get_settings(const char *filename) -> Settings
auto main(int argc, char *argv[]) -> int
{
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::warning);
//boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::warning);
if (argc != 2) {
BOOST_LOG_TRIVIAL(error) << "Bad arguments";
return 1;

31
myirc/CMakeLists.txt Normal file
View File

@ -0,0 +1,31 @@
add_custom_command(
OUTPUT irc_commands.inc
COMMAND
gperf
-C -Z IrcCommandHash -K text -L C++ -t
--output-file irc_commands.inc
${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/irc_commands.gperf
VERBATIM)
add_library(myirc STATIC
irc_commands.inc
bot.cpp
challenge.cpp
client.cpp
connection.cpp
ircmsg.cpp
openssl_utils.cpp
registration.cpp
sasl_mechanism.cpp
snote.cpp
)
target_include_directories(myirc PUBLIC include)
target_include_directories(myirc PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(myirc PRIVATE
OpenSSL::SSL
Boost::signals2 Boost::log Boost::asio
tomlplusplus_tomlplusplus
PkgConfig::LIBHS
mysocks5 mybase64)

View File

@ -224,6 +224,16 @@ auto Client::is_channel(std::string_view name) const -> bool
return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos;
}
namespace {
template <class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
}
auto Client::on_authenticate(const std::string_view body) -> void
{
if (not sasl_mechanism_)
@ -233,16 +243,23 @@ auto Client::on_authenticate(const std::string_view body) -> void
return;
}
if (auto reply = sasl_mechanism_->step(body))
std::visit(
overloaded{
[this](const std::string &reply) {
connection_.send_authenticate_encoded(reply);
},
[this](SaslMechanism::NoReply) {
connection_.send_authenticate("*"sv);
},
[this](SaslMechanism::Failure) {
connection_.send_authenticate_abort();
},
},
sasl_mechanism_->step(body));
if (sasl_mechanism_->is_complete())
{
connection_.send_authenticate_encoded(*reply);
// Clean up completed SASL transactions
if (sasl_mechanism_->is_complete())
{
sasl_mechanism_.reset();
}
sasl_mechanism_.reset();
}
}

View File

@ -467,7 +467,7 @@ auto build_ssl_context(
}
auto Connection::connect(
ConnectSettings settings
Settings settings
) -> boost::asio::awaitable<void>
{
using namespace std::placeholders;
@ -539,7 +539,7 @@ auto Connection::connect(
stream_.close();
}
auto Connection::start(ConnectSettings settings) -> void
auto Connection::start(Settings settings) -> void
{
boost::asio::co_spawn(
stream_.get_executor(), connect(std::move(settings)),

View File

@ -6,18 +6,18 @@
#include <memory>
struct Command
{
std::string_view source;
std::string_view target;
std::string_view oper;
std::string_view account;
std::string_view command;
std::string_view arguments;
};
struct Bot : std::enable_shared_from_this<Bot>
{
struct Command
{
std::string_view source;
std::string_view target;
std::string_view oper;
std::string_view account;
std::string_view command;
std::string_view arguments;
};
std::shared_ptr<Client> self_;
char command_prefix_;

View File

@ -10,5 +10,7 @@ struct CCallback_<R (F::*) (Ts...) const>
}
};
/// @brief Wrapper for passing closures through C-style callbacks.
/// @tparam F Type of the closure
template <typename F>
using CCallback = CCallback_<decltype(&F::operator())>;

View File

@ -8,6 +8,7 @@
#include <memory>
#include <string>
/// @brief Implements the CHALLENGE command protocol to identify as an operator.
class Challenge : std::enable_shared_from_this<Challenge>
{
EVP_PKEY_Ref key_;
@ -20,5 +21,11 @@ class Challenge : std::enable_shared_from_this<Challenge>
public:
Challenge(EVP_PKEY_Ref, Connection &);
static auto start(Connection &, std::string_view user, EVP_PKEY_Ref) -> std::shared_ptr<Challenge>;
/// @brief Starts the CHALLENGE protocol.
/// @param connection Registered connection.
/// @param user Operator username
/// @param key Operator private RSA key
/// @return Handle to the challenge object.
static auto start(Connection &, std::string_view user, EVP_PKEY_Ref key) -> std::shared_ptr<Challenge>;
};

View File

@ -13,25 +13,26 @@
#include <memory>
#include <string>
struct ConnectSettings
{
bool tls;
std::string host;
std::uint16_t port;
X509_Ref client_cert;
EVP_PKEY_Ref client_key;
std::string verify;
std::string sni;
std::string socks_host;
std::uint16_t socks_port;
std::string socks_user;
std::string socks_pass;
};
class Connection : public std::enable_shared_from_this<Connection>
{
public:
struct Settings
{
bool tls;
std::string host;
std::uint16_t port;
X509_Ref client_cert;
EVP_PKEY_Ref client_key;
std::string verify;
std::string sni;
std::string socks_host;
std::uint16_t socks_port;
std::string socks_user;
std::string socks_pass;
};
private:
Stream stream_;
boost::asio::steady_timer watchdog_timer_;
@ -52,7 +53,7 @@ private:
auto watchdog() -> void;
auto watchdog_activity() -> void;
auto connect(ConnectSettings 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
@ -78,7 +79,7 @@ public:
return stream_.get_executor();
}
auto start(ConnectSettings) -> void;
auto start(Settings) -> void;
auto close() -> void;
auto send_ping(std::string_view) -> void;

View File

@ -33,8 +33,10 @@ struct Ref : std::unique_ptr<T, FnDeleter<T>> {
*this = ref;
}
Ref &operator=(const Ref &ref) {
RefTraits<T>::UpRef(ref.get());
this->reset(ref.get());
if (ref) {
RefTraits<T>::UpRef(ref.get());
this->reset(ref.get());
}
return *this;
}
};

View File

@ -2,7 +2,7 @@
#include "connection.hpp"
#include "client.hpp"
#include "settings.hpp"
#include "ref.hpp"
#include <memory>
#include <string>
@ -11,7 +11,21 @@
class Registration : public std::enable_shared_from_this<Registration>
{
const Settings &settings_;
public:
struct Settings {
std::string nickname;
std::string username;
std::string realname;
std::string password;
std::string sasl_mechanism;
std::string sasl_authcid;
std::string sasl_authzid;
std::string sasl_password;
EVP_PKEY_Ref sasl_key;
};
private:
Settings settings_;
std::shared_ptr<Client> client_;
boost::signals2::scoped_connection slot_;
@ -27,12 +41,12 @@ class Registration : public std::enable_shared_from_this<Registration>
public:
Registration(
const Settings &,
Settings,
std::shared_ptr<Client>
);
static auto start(
const Settings &,
Settings,
std::shared_ptr<Client>
) -> std::shared_ptr<Registration>;
};

View File

@ -6,14 +6,20 @@
#include <optional>
#include <string>
#include <string_view>
#include <variant>
class SaslMechanism
{
public:
struct NoReply {};
struct Failure {};
using StepResult = std::variant<std::string, NoReply, Failure>;
virtual ~SaslMechanism() {}
virtual auto mechanism_name() const -> std::string = 0;
virtual auto step(std::string_view msg) -> std::optional<std::string> = 0;
virtual auto step(std::string_view msg) -> StepResult = 0;
virtual auto is_complete() const -> bool = 0;
};
@ -37,7 +43,7 @@ public:
return "PLAIN";
}
auto step(std::string_view msg) -> std::optional<std::string> override;
auto step(std::string_view msg) -> StepResult override;
auto is_complete() const -> bool override
{
@ -61,7 +67,7 @@ public:
return "EXTERNAL";
}
auto step(std::string_view msg) -> std::optional<std::string> override;
auto step(std::string_view msg) -> StepResult override;
auto is_complete() const -> bool override
{

View File

@ -9,10 +9,10 @@
#include <unordered_map>
Registration::Registration(
const Settings &settings,
Settings settings,
std::shared_ptr<Client> client
)
: settings_{settings}
: settings_{std::move(settings)}
, client_{std::move(client)}
{
}
@ -95,7 +95,7 @@ auto Registration::on_cap_list(const std::unordered_map<std::string, std::string
}
auto Registration::start(
const Settings &settings,
Settings settings,
std::shared_ptr<Client> client
) -> std::shared_ptr<Registration>
{

View File

@ -1,8 +1,8 @@
#include "sasl_mechanism.hpp"
auto SaslPlain::step(std::string_view msg) -> std::optional<std::string> {
auto SaslPlain::step(std::string_view msg) -> StepResult {
if (complete_) {
return std::nullopt;
return Failure{};
} else {
std::string reply;
@ -14,14 +14,14 @@ auto SaslPlain::step(std::string_view msg) -> std::optional<std::string> {
complete_ = true;
return {std::move(reply)};
return std::move(reply);
}
}
auto SaslExternal::step(std::string_view msg) -> std::optional<std::string> {
auto SaslExternal::step(std::string_view msg) -> StepResult {
if (complete_) {
return std::nullopt;
return Failure{};
} else {
return {std::move(authzid_)};
return std::move(authzid_);
}
}

View File

@ -73,7 +73,7 @@ const SnotePattern static patterns[] = {
{SnoteTag::KilledRemoteOper,
R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) Path: ([^ ]+)!([^ ]+)!([^ ]+)!([^ ]+) (.*)$)"},
{SnoteTag::Killed,
R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) (.*)$)"},
@ -90,11 +90,11 @@ const SnotePattern static patterns[] = {
R"(^([^ ]+) \(([^ ]+)!([^ ]+)@([^ ]+)\) is now an operator$)"},
{SnoteTag::OperspyWhois,
R"(^OPERSPY ([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} WHOIS ([^ ]+)!([^ ]+)@([^ ]+) ([^ ]+)$)"},
R"(^OPERSPY ([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} WHOIS ([^ ]+)!([^ ]+)@([^ ]+) ([^ ]+) $)"}, // trailing space intentional
{SnoteTag::Freeze,
"^\x02([^ ]+)\x02 froze the account \x02([^ ]+)\x02 \\((.*)\\)\\.$"},
{SnoteTag::DroppedChannel,
"^\x02([^ ]+)\x02 dropped the channel \x02([^ ]+)\x02$"},
@ -112,28 +112,28 @@ const SnotePattern static patterns[] = {
{SnoteTag::TemporaryDline,
R"(^([^ ]+) added temporary ([^ ]+) min\. D-Line for \[([^ ]+)\] \[(.*)\]$)"},
{SnoteTag::FailedChallengeMissingSecure,
R"(^Failed CHALLENGE attempt - missing secure connection by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::FailedChallenge,
R"(^Failed CHALLENGE attempt by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::FailedChallengeHostMismatch,
R"(^Failed CHALLENGE attempt - host mismatch by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::FailedChallengeNoBlock,
R"(^Failed CHALLENGE attempt - user@host mismatch or no operator block for ([^ ]+) by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::FailedChallengeTls,
R"(^Failed CHALLENGE attempt - missing SSL/TLS by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::FailedChallengeFingerprintMismatch,
R"(^Failed CHALLENGE attempt - client certificate fingerprint mismatch by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::SighupReloadingConf,
R"(^Got signal SIGHUP, reloading ircd conf\. file$)"},
{SnoteTag::JoinedJuped,
R"(^User ([^ ]+) \(([^ ]+)@([^ ]+)\) is attempting to join locally juped channel ([^ ]+) \((.*)\)$)"},