Compare commits
No commits in common. "fd4612d385962db5fe53a21f53582b7911b4b006" and "1a1deb03b7f02e277c4071893960169fa8518055" have entirely different histories.
fd4612d385
...
1a1deb03b7
@ -6,24 +6,16 @@ project(xbot
|
|||||||
)
|
)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
find_package(Boost 1.83.0 CONFIG COMPONENTS log)
|
||||||
pkg_check_modules(LIBHS libhs REQUIRED IMPORTED_TARGET)
|
pkg_check_modules(LIBHS libhs REQUIRED IMPORTED_TARGET)
|
||||||
|
|
||||||
set(BOOST_INCLUDE_LIBRARIES asio log signals2)
|
|
||||||
set(BOOST_ENABLE_CMAKE ON)
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
|
||||||
Boost
|
|
||||||
URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.tar.xz
|
|
||||||
URL_HASH SHA1=4ec86f884ffb57ce7f6a6c6eb05b1af247aba0ac
|
|
||||||
)
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
tomlplusplus
|
tomlplusplus
|
||||||
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
||||||
GIT_TAG v3.4.0
|
GIT_TAG v3.4.0
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(tomlplusplus)
|
FetchContent_MakeAvailable(tomlplusplus)
|
||||||
FetchContent_MakeAvailable(Boost)
|
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT irc_commands.inc
|
OUTPUT irc_commands.inc
|
||||||
@ -42,9 +34,8 @@ add_executable(xbot
|
|||||||
registration_thread.cpp
|
registration_thread.cpp
|
||||||
snote.cpp
|
snote.cpp
|
||||||
self_thread.cpp
|
self_thread.cpp
|
||||||
sasl_mechanism.cpp
|
|
||||||
irc_coroutine.cpp
|
irc_coroutine.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(xbot PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
target_include_directories(xbot PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
target_link_libraries(xbot PRIVATE Boost::signals2 Boost::log Boost::asio tomlplusplus_tomlplusplus PkgConfig::LIBHS mybase64)
|
target_link_libraries(xbot PRIVATE Boost::log Boost::boost tomlplusplus_tomlplusplus PkgConfig::LIBHS mybase64)
|
||||||
|
53
command_thread.cpp
Normal file
53
command_thread.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "command_thread.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
#include "irc_parse_thread.hpp"
|
||||||
|
#include "ircmsg.hpp"
|
||||||
|
|
||||||
|
auto CommandThread::start(Connection& connection) -> void
|
||||||
|
{
|
||||||
|
connection.add_listener<IrcMsgEvent>([&connection](IrcMsgEvent& event)
|
||||||
|
{
|
||||||
|
if (IrcCommand::PRIVMSG != event.command) return;
|
||||||
|
auto const message = event.irc.args[1];
|
||||||
|
if (message.empty()) return;
|
||||||
|
if ('!' != message.front()) return;
|
||||||
|
|
||||||
|
CommandEvent command_event;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const cmdend = message.find(' ');
|
||||||
|
if (std::string_view::npos == cmdend)
|
||||||
|
{
|
||||||
|
command_event.command = message.substr(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
command_event.command = message.substr(1, cmdend - 1);
|
||||||
|
command_event.arg = message.substr(cmdend + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const nickend = event.irc.source.find('!');
|
||||||
|
if (std::string_view::npos != nickend)
|
||||||
|
{
|
||||||
|
command_event.nick = event.irc.source.substr(0, nickend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& [k,v] : event.irc.tags)
|
||||||
|
{
|
||||||
|
if ("solanum.chat/oper" == k)
|
||||||
|
{
|
||||||
|
command_event.oper = v;
|
||||||
|
}
|
||||||
|
else if ("account" == k)
|
||||||
|
{
|
||||||
|
command_event.account = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.dispatch(command_event);
|
||||||
|
});
|
||||||
|
}
|
30
command_thread.hpp
Normal file
30
command_thread.hpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "event.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
class Connection;
|
||||||
|
|
||||||
|
struct CommandEvent : Event
|
||||||
|
{
|
||||||
|
/// @brief oper account of sender
|
||||||
|
std::string_view oper;
|
||||||
|
|
||||||
|
/// @brief nickserv acccount of sender
|
||||||
|
std::string_view account;
|
||||||
|
|
||||||
|
/// @brief nickname of sender
|
||||||
|
std::string_view nick;
|
||||||
|
|
||||||
|
/// @brief command name excluding sigil
|
||||||
|
std::string_view command;
|
||||||
|
|
||||||
|
/// @brief complete argument excluding space after command
|
||||||
|
std::string_view arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandThread
|
||||||
|
{
|
||||||
|
static auto start(Connection&) -> void;
|
||||||
|
};
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "linebuffer.hpp"
|
#include "linebuffer.hpp"
|
||||||
|
|
||||||
#include <mybase64.hpp>
|
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -58,8 +56,7 @@ auto Connection::connect(
|
|||||||
{
|
{
|
||||||
auto resolver = boost::asio::ip::tcp::resolver{io};
|
auto resolver = boost::asio::ip::tcp::resolver{io};
|
||||||
auto const endpoints = co_await resolver.async_resolve(host, port, boost::asio::use_awaitable);
|
auto const endpoints = co_await resolver.async_resolve(host, port, boost::asio::use_awaitable);
|
||||||
auto const endpoint = co_await boost::asio::async_connect(stream_, endpoints, boost::asio::use_awaitable);
|
co_await boost::asio::async_connect(stream_, endpoints, boost::asio::use_awaitable);
|
||||||
BOOST_LOG_TRIVIAL(debug) << "CONNECTED: " << endpoint;
|
|
||||||
|
|
||||||
sig_connect();
|
sig_connect();
|
||||||
}
|
}
|
||||||
@ -139,10 +136,6 @@ 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
|
// Server notice generate snote events but not IRC command events
|
||||||
case IrcCommand::NOTICE:
|
case IrcCommand::NOTICE:
|
||||||
if (auto match = snoteCore.match(msg)) {
|
if (auto match = snoteCore.match(msg)) {
|
||||||
@ -258,56 +251,3 @@ auto Connection::send_authenticate(std::string_view message) -> void
|
|||||||
{
|
{
|
||||||
write_irc("AUTHENTICATE", message);
|
write_irc("AUTHENTICATE", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,9 +23,6 @@ private:
|
|||||||
// Set false when message received.
|
// Set false when message received.
|
||||||
bool stalled_;
|
bool stalled_;
|
||||||
|
|
||||||
// AUTHENTICATE support
|
|
||||||
std::string authenticate_buffer_;
|
|
||||||
|
|
||||||
auto write_buffers() -> void;
|
auto write_buffers() -> void;
|
||||||
auto dispatch_line(char * line) -> void;
|
auto dispatch_line(char * line) -> void;
|
||||||
|
|
||||||
@ -49,7 +46,6 @@ public:
|
|||||||
boost::signals2::signal<void()> sig_disconnect;
|
boost::signals2::signal<void()> sig_disconnect;
|
||||||
boost::signals2::signal<void(IrcCommand, const IrcMsg &)> sig_ircmsg;
|
boost::signals2::signal<void(IrcCommand, const IrcMsg &)> sig_ircmsg;
|
||||||
boost::signals2::signal<void(SnoteMatch &)> sig_snote;
|
boost::signals2::signal<void(SnoteMatch &)> sig_snote;
|
||||||
boost::signals2::signal<void(std::string_view)> sig_authenticate;
|
|
||||||
|
|
||||||
auto get_executor() -> boost::asio::any_io_executor {
|
auto get_executor() -> boost::asio::any_io_executor {
|
||||||
return stream_.get_executor();
|
return stream_.get_executor();
|
||||||
@ -63,8 +59,6 @@ public:
|
|||||||
|
|
||||||
auto close() -> void;
|
auto close() -> void;
|
||||||
|
|
||||||
auto on_authenticate(std::string_view) -> void;
|
|
||||||
|
|
||||||
auto send_ping(std::string_view) -> void;
|
auto send_ping(std::string_view) -> void;
|
||||||
auto send_pong(std::string_view) -> void;
|
auto send_pong(std::string_view) -> void;
|
||||||
auto send_pass(std::string_view) -> void;
|
auto send_pass(std::string_view) -> void;
|
||||||
@ -76,8 +70,6 @@ public:
|
|||||||
auto send_privmsg(std::string_view, std::string_view) -> void;
|
auto send_privmsg(std::string_view, std::string_view) -> void;
|
||||||
auto send_notice(std::string_view, 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(std::string_view message) -> void;
|
||||||
auto send_authenticate_encoded(std::string_view message) -> void;
|
|
||||||
auto send_authenticate_abort() -> void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
|
6
event.hpp
Normal file
6
event.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
virtual ~Event() = default;
|
||||||
|
bool handled_ = false;
|
||||||
|
};
|
@ -8,7 +8,7 @@ struct RecognizedCommand {
|
|||||||
001, IrcCommand::RPL_WELCOME, 2, 2
|
001, IrcCommand::RPL_WELCOME, 2, 2
|
||||||
002, IrcCommand::RPL_YOURHOST, 2, 2
|
002, IrcCommand::RPL_YOURHOST, 2, 2
|
||||||
003, IrcCommand::RPL_CREATED, 2, 2
|
003, IrcCommand::RPL_CREATED, 2, 2
|
||||||
004, IrcCommand::RPL_MYINFO, 5, 6
|
004, IrcCommand::RPL_MYINFO, 5, 5
|
||||||
005, IrcCommand::RPL_ISUPPORT, 2, 15
|
005, IrcCommand::RPL_ISUPPORT, 2, 15
|
||||||
008, IrcCommand::RPL_SNOMASK, 3, 3
|
008, IrcCommand::RPL_SNOMASK, 3, 3
|
||||||
010, IrcCommand::RPL_REDIR, 4, 4
|
010, IrcCommand::RPL_REDIR, 4, 4
|
||||||
@ -47,12 +47,12 @@ struct RecognizedCommand {
|
|||||||
247, IrcCommand::RPL_STATSXLINE
|
247, IrcCommand::RPL_STATSXLINE
|
||||||
248, IrcCommand::RPL_STATSULINE
|
248, IrcCommand::RPL_STATSULINE
|
||||||
249, IrcCommand::RPL_STATSDEBUG
|
249, IrcCommand::RPL_STATSDEBUG
|
||||||
250, IrcCommand::RPL_STATSCONN, 2, 2
|
250, IrcCommand::RPL_STATSCONN
|
||||||
251, IrcCommand::RPL_LUSERCLIENT, 2, 2
|
251, IrcCommand::RPL_LUSERCLIENT
|
||||||
252, IrcCommand::RPL_LUSEROP
|
252, IrcCommand::RPL_LUSEROP
|
||||||
253, IrcCommand::RPL_LUSERUNKNOWN
|
253, IrcCommand::RPL_LUSERUNKNOWN
|
||||||
254, IrcCommand::RPL_LUSERCHANNELS
|
254, IrcCommand::RPL_LUSERCHANNELS
|
||||||
255, IrcCommand::RPL_LUSERME, 2, 2
|
255, IrcCommand::RPL_LUSERME
|
||||||
256, IrcCommand::RPL_ADMINME, 3, 3
|
256, IrcCommand::RPL_ADMINME, 3, 3
|
||||||
257, IrcCommand::RPL_ADMINLOC1, 2, 2
|
257, IrcCommand::RPL_ADMINLOC1, 2, 2
|
||||||
258, IrcCommand::RPL_ADMINLOC2, 2, 2
|
258, IrcCommand::RPL_ADMINLOC2, 2, 2
|
||||||
@ -248,15 +248,15 @@ struct RecognizedCommand {
|
|||||||
744, IrcCommand::ERR_TOPICLOCK
|
744, IrcCommand::ERR_TOPICLOCK
|
||||||
750, IrcCommand::RPL_SCANMATCHED
|
750, IrcCommand::RPL_SCANMATCHED
|
||||||
751, IrcCommand::RPL_SCANUMODES
|
751, IrcCommand::RPL_SCANUMODES
|
||||||
900, IrcCommand::RPL_LOGGEDIN, 4, 4
|
900, IrcCommand::RPL_LOGGEDIN
|
||||||
901, IrcCommand::RPL_LOGGEDOUT, 3, 3
|
901, IrcCommand::RPL_LOGGEDOUT
|
||||||
902, IrcCommand::ERR_NICKLOCKED, 2, 2
|
902, IrcCommand::ERR_NICKLOCKED
|
||||||
903, IrcCommand::RPL_SASLSUCCESS, 2, 2
|
903, IrcCommand::RPL_SASLSUCCESS
|
||||||
904, IrcCommand::ERR_SASLFAIL, 2, 2
|
904, IrcCommand::ERR_SASLFAIL
|
||||||
905, IrcCommand::ERR_SASLTOOLONG, 2, 2
|
905, IrcCommand::ERR_SASLTOOLONG
|
||||||
906, IrcCommand::ERR_SASLABORTED, 2, 2
|
906, IrcCommand::ERR_SASLABORTED
|
||||||
907, IrcCommand::ERR_SASLALREADY, 2, 2
|
907, IrcCommand::ERR_SASLALREADY
|
||||||
908, IrcCommand::RPL_SASLMECHS, 3, 3
|
908, IrcCommand::RPL_SASLMECHS
|
||||||
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
|
||||||
|
4
main.cpp
4
main.cpp
@ -17,8 +17,8 @@ auto start(boost::asio::io_context & io, Settings const& settings) -> void
|
|||||||
{
|
{
|
||||||
auto const connection = std::make_shared<Connection>(io);
|
auto const connection = std::make_shared<Connection>(io);
|
||||||
|
|
||||||
auto const selfThread = SelfThread::start(*connection);
|
RegistrationThread::start(*connection, settings.password, settings.username, settings.realname, settings.nickname);
|
||||||
RegistrationThread::start(*connection, settings, selfThread);
|
auto selfThread = SelfThread::start(*connection);
|
||||||
|
|
||||||
connection->sig_snote.connect([](auto &match) {
|
connection->sig_snote.connect([](auto &match) {
|
||||||
std::cout << "SNOTE " << static_cast<int>(match.get_tag()) << std::endl;
|
std::cout << "SNOTE " << static_cast<int>(match.get_tag()) << std::endl;
|
||||||
|
15
ping_thread.cpp
Normal file
15
ping_thread.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "ping_thread.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
#include "write_irc.hpp"
|
||||||
|
|
||||||
|
auto PingThread::start(Connection& connection) -> void
|
||||||
|
{
|
||||||
|
connection.sig_ircmsg.connect([&connection](auto cmd, auto& msg)
|
||||||
|
{
|
||||||
|
if (IrcCommand::PING == cmd)
|
||||||
|
{
|
||||||
|
send_pong(connection, msg.args[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
8
ping_thread.hpp
Normal file
8
ping_thread.hpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Connection;
|
||||||
|
|
||||||
|
struct PingThread
|
||||||
|
{
|
||||||
|
static auto start(Connection& connection) -> void;
|
||||||
|
};
|
224
priv_thread.cpp
Normal file
224
priv_thread.cpp
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
#include "priv_thread.hpp"
|
||||||
|
|
||||||
|
#include "command_thread.hpp"
|
||||||
|
#include "connection.hpp"
|
||||||
|
#include "write_irc.hpp"
|
||||||
|
|
||||||
|
#include <toml++/toml.hpp>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
PrivThread::PrivThread(Connection& connection, std::string config_path)
|
||||||
|
: connection_{connection}
|
||||||
|
, config_path_{std::move(config_path)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::check_command(CommandEvent& event, std::string priv) -> bool
|
||||||
|
{
|
||||||
|
auto check_priv = [&priv](auto& map, std::string const& name) {
|
||||||
|
auto const cursor = map.find(name);
|
||||||
|
return cursor != map.end() && cursor->second.contains(priv);
|
||||||
|
};
|
||||||
|
auto check = [&check_priv](auto& map, auto& name) {
|
||||||
|
return
|
||||||
|
(not name.empty() &&
|
||||||
|
(check_priv(map, wildcard) ||
|
||||||
|
check_priv(map, std::string{name})));
|
||||||
|
};
|
||||||
|
return check(oper_privs_, event.oper) || check(account_privs_, event.account);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::list_privs(std::string_view nick) -> void
|
||||||
|
{
|
||||||
|
for (auto const& [oper, privset] : oper_privs_)
|
||||||
|
{
|
||||||
|
std::string message = "Oper ";
|
||||||
|
message += oper;
|
||||||
|
message += ":";
|
||||||
|
|
||||||
|
for (auto const& priv : privset)
|
||||||
|
{
|
||||||
|
message += " ";
|
||||||
|
message += priv;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_notice(connection_, nick, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& [account, privset] : account_privs_)
|
||||||
|
{
|
||||||
|
std::string message = "Account ";
|
||||||
|
message += account;
|
||||||
|
message += ":";
|
||||||
|
|
||||||
|
for (auto const& priv : privset)
|
||||||
|
{
|
||||||
|
message += " ";
|
||||||
|
message += priv;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_notice(connection_, nick, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::on_command(CommandEvent& event) -> void
|
||||||
|
{
|
||||||
|
if ("list_privs" == event.command)
|
||||||
|
{
|
||||||
|
if (check_command(event, PrivThread::owner_priv))
|
||||||
|
{
|
||||||
|
list_privs(event.nick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("add_priv" == event.command)
|
||||||
|
{
|
||||||
|
if (check_command(event, PrivThread::owner_priv))
|
||||||
|
{
|
||||||
|
auto in = std::istringstream{std::string{event.arg}};
|
||||||
|
std::string kind, name, priv;
|
||||||
|
if (in >> kind >> name >> priv)
|
||||||
|
{
|
||||||
|
if ("oper" == kind)
|
||||||
|
{
|
||||||
|
oper_privs_[name].insert(priv);
|
||||||
|
save_config();
|
||||||
|
send_notice(connection_, event.nick, "ack");
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Priv added. by:" << event.nick << " oper:" << name << " priv:" << priv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ("account" == kind)
|
||||||
|
{
|
||||||
|
account_privs_[name].insert(priv);
|
||||||
|
save_config();
|
||||||
|
send_notice(connection_, event.nick, "ack");
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Priv added. by:" << event.nick << " account:" << name << " priv:" << priv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send_notice(connection_, event.nick, "nak");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("remove_priv" == event.command)
|
||||||
|
{
|
||||||
|
if (check_command(event, PrivThread::owner_priv))
|
||||||
|
{
|
||||||
|
auto in = std::istringstream{std::string{event.arg}};
|
||||||
|
std::string kind, name, priv;
|
||||||
|
if (in >> kind >> name >> priv)
|
||||||
|
{
|
||||||
|
if ("oper" == kind)
|
||||||
|
{
|
||||||
|
if (wildcard == priv)
|
||||||
|
{
|
||||||
|
oper_privs_.erase(name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oper_privs_[name].erase(priv);
|
||||||
|
if (oper_privs_[name].empty()) oper_privs_.erase(name);
|
||||||
|
}
|
||||||
|
save_config();
|
||||||
|
send_notice(connection_, event.nick, "ack");
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Priv removed. by:" << event.nick << " oper:" << name << " priv:" << priv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ("account" == kind)
|
||||||
|
{
|
||||||
|
if (wildcard == priv)
|
||||||
|
{
|
||||||
|
account_privs_.erase(name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
account_privs_[name].erase(priv);
|
||||||
|
if (account_privs_[name].empty()) account_privs_.erase(name);
|
||||||
|
}
|
||||||
|
save_config();
|
||||||
|
send_notice(connection_, event.nick, "ack");
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Priv removed. by:" << event.nick << " account:" << name << " priv:" << priv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send_notice(connection_, event.nick, "nak");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::save_config() -> void
|
||||||
|
{
|
||||||
|
auto config = toml::table{};
|
||||||
|
|
||||||
|
auto serialize_table = [&config](auto name, auto& map) {
|
||||||
|
if (not map.empty()) {
|
||||||
|
auto tab = toml::table{};
|
||||||
|
for (auto const& [oper, privs] : map)
|
||||||
|
{
|
||||||
|
auto privset = toml::array{};
|
||||||
|
for (auto const& priv : privs)
|
||||||
|
{
|
||||||
|
privset.push_back(priv);
|
||||||
|
}
|
||||||
|
tab.insert(oper, privset);
|
||||||
|
}
|
||||||
|
config.insert(name, tab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
serialize_table("oper", oper_privs_);
|
||||||
|
serialize_table("account", account_privs_);
|
||||||
|
|
||||||
|
std::string tmp = config_path_ + ".tmp";
|
||||||
|
std::ofstream{tmp} << config;
|
||||||
|
std::filesystem::rename(tmp, config_path_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::load_config() -> void
|
||||||
|
{
|
||||||
|
if (auto in = std::ifstream{config_path_})
|
||||||
|
{
|
||||||
|
auto const config = toml::parse(in);
|
||||||
|
|
||||||
|
if (config.contains("oper"))
|
||||||
|
{
|
||||||
|
for (auto const [oper, privs] : *config["oper"].as_table())
|
||||||
|
{
|
||||||
|
auto& privset = oper_privs_[std::string{oper.str()}];
|
||||||
|
for (auto const& priv : *privs.as_array())
|
||||||
|
{
|
||||||
|
privset.insert(priv.as_string()->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.contains("account"))
|
||||||
|
{
|
||||||
|
for (auto const [account, privs] : *config["account"].as_table())
|
||||||
|
{
|
||||||
|
auto& privset = account_privs_[std::string{account.str()}];
|
||||||
|
for (auto const& priv : *privs.as_array())
|
||||||
|
{
|
||||||
|
privset.insert(priv.as_string()->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PrivThread::start(Connection& connection, std::string config_path) -> std::shared_ptr<PrivThread>
|
||||||
|
{
|
||||||
|
auto thread = std::make_shared<PrivThread>(connection, config_path);
|
||||||
|
|
||||||
|
thread->load_config();
|
||||||
|
|
||||||
|
connection.add_listener<CommandEvent>([thread](CommandEvent& event)
|
||||||
|
{
|
||||||
|
thread->on_command(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
}
|
33
priv_thread.hpp
Normal file
33
priv_thread.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
class Connection;
|
||||||
|
class CommandEvent;
|
||||||
|
|
||||||
|
class PrivThread : std::enable_shared_from_this<PrivThread>
|
||||||
|
{
|
||||||
|
Connection& connection_;
|
||||||
|
std::string config_path_;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unordered_set<std::string>> oper_privs_;
|
||||||
|
std::unordered_map<std::string, std::unordered_set<std::string>> account_privs_;
|
||||||
|
|
||||||
|
auto on_command(CommandEvent&) -> void;
|
||||||
|
|
||||||
|
auto load_config() -> void;
|
||||||
|
auto save_config() -> void;
|
||||||
|
auto list_privs(std::string_view nick) -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PrivThread(Connection&, std::string config_path);
|
||||||
|
auto check_command(CommandEvent& event, std::string priv) -> bool;
|
||||||
|
|
||||||
|
static constexpr char const* owner_priv = "owner";
|
||||||
|
static constexpr char const* wildcard = "*";
|
||||||
|
|
||||||
|
static auto start(Connection&, std::string config_path) -> std::shared_ptr<PrivThread>;
|
||||||
|
};
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
#include "ircmsg.hpp"
|
#include "ircmsg.hpp"
|
||||||
#include "sasl_mechanism.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -10,27 +9,31 @@
|
|||||||
|
|
||||||
RegistrationThread::RegistrationThread(
|
RegistrationThread::RegistrationThread(
|
||||||
Connection& connection,
|
Connection& connection,
|
||||||
const Settings &settings,
|
std::string password,
|
||||||
std::shared_ptr<SelfThread> self
|
std::string username,
|
||||||
|
std::string realname,
|
||||||
|
std::string nickname
|
||||||
)
|
)
|
||||||
: connection_{connection}
|
: connection_{connection}
|
||||||
, settings_{settings}
|
, password_{std::move(password)}
|
||||||
, self_{std::move(self)}
|
, username_{std::move(username)}
|
||||||
|
, realname_{std::move(realname)}
|
||||||
|
, nickname_{std::move(nickname)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RegistrationThread::on_connect() -> void
|
auto RegistrationThread::on_connect() -> void
|
||||||
{
|
{
|
||||||
connection_.send_cap_ls();
|
connection_.send_cap_ls();
|
||||||
connection_.send_pass(settings_.password);
|
connection_.send_pass(password_);
|
||||||
connection_.send_user(settings_.username, settings_.realname);
|
connection_.send_user(username_, realname_);
|
||||||
connection_.send_nick(settings_.nickname);
|
connection_.send_nick(nickname_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RegistrationThread::send_req() -> void
|
auto RegistrationThread::send_req() -> void
|
||||||
{
|
{
|
||||||
std::string request;
|
std::string request;
|
||||||
std::vector<char const*> want {
|
char const* const want[] = {
|
||||||
"account-notify",
|
"account-notify",
|
||||||
"account-tag",
|
"account-tag",
|
||||||
"batch",
|
"batch",
|
||||||
@ -46,10 +49,6 @@ auto RegistrationThread::send_req() -> void
|
|||||||
"solanum.chat/realhost",
|
"solanum.chat/realhost",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (settings_.sasl_mechanism == "PLAIN") {
|
|
||||||
want.push_back("sasl");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const cap : want)
|
for (auto const cap : want)
|
||||||
{
|
{
|
||||||
if (caps.contains(cap))
|
if (caps.contains(cap))
|
||||||
@ -86,21 +85,7 @@ auto RegistrationThread::on_msg_cap_ack(IrcMsg const& msg) -> void
|
|||||||
if (outstanding.empty())
|
if (outstanding.empty())
|
||||||
{
|
{
|
||||||
message_handle_.disconnect();
|
message_handle_.disconnect();
|
||||||
|
|
||||||
if (settings_.sasl_mechanism.empty()) {
|
|
||||||
connection_.send_cap_end();
|
connection_.send_cap_end();
|
||||||
} else {
|
|
||||||
self_->start_sasl(std::make_unique<SaslPlain>(settings_.sasl_authcid, settings_.sasl_authzid, settings_.sasl_password));
|
|
||||||
connection_.sig_ircmsg.connect_extended([thread = shared_from_this()](auto &slot, auto cmd, auto &msg) {
|
|
||||||
switch (cmd) {
|
|
||||||
default: break;
|
|
||||||
case IrcCommand::RPL_SASLSUCCESS:
|
|
||||||
case IrcCommand::ERR_SASLFAIL:
|
|
||||||
thread->connection_.send_cap_end();
|
|
||||||
slot.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,11 +136,13 @@ auto RegistrationThread::on_msg_cap_ls(IrcMsg const& msg) -> void
|
|||||||
|
|
||||||
auto RegistrationThread::start(
|
auto RegistrationThread::start(
|
||||||
Connection& connection,
|
Connection& connection,
|
||||||
const Settings &settings,
|
std::string password,
|
||||||
std::shared_ptr<SelfThread> self
|
std::string username,
|
||||||
|
std::string realname,
|
||||||
|
std::string nickname
|
||||||
) -> std::shared_ptr<RegistrationThread>
|
) -> std::shared_ptr<RegistrationThread>
|
||||||
{
|
{
|
||||||
auto const thread = std::make_shared<RegistrationThread>(connection, std::move(settings), std::move(self));
|
auto const thread = std::make_shared<RegistrationThread>(connection, password, username, realname, nickname);
|
||||||
|
|
||||||
thread->listen_for_cap_ls();
|
thread->listen_for_cap_ls();
|
||||||
|
|
||||||
@ -172,7 +159,7 @@ auto RegistrationThread::listen_for_cap_ack() -> void
|
|||||||
{
|
{
|
||||||
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, IrcMsg const& msg)
|
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, IrcMsg const& msg)
|
||||||
{
|
{
|
||||||
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "ACK" == msg.args[1])
|
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "*" == msg.args[0] && "ACK" == msg.args[1])
|
||||||
{
|
{
|
||||||
thread->on_msg_cap_ack(msg);
|
thread->on_msg_cap_ack(msg);
|
||||||
}
|
}
|
||||||
@ -183,7 +170,7 @@ auto RegistrationThread::listen_for_cap_ls() -> void
|
|||||||
{
|
{
|
||||||
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, IrcMsg const& msg)
|
message_handle_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, IrcMsg const& msg)
|
||||||
{
|
{
|
||||||
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "LS" == msg.args[1])
|
if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "*" == msg.args[0] && "LS" == msg.args[1])
|
||||||
{
|
{
|
||||||
thread->on_msg_cap_ls(msg);
|
thread->on_msg_cap_ls(msg);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
#include "settings.hpp"
|
|
||||||
#include "self_thread.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -12,8 +10,10 @@
|
|||||||
class RegistrationThread : public std::enable_shared_from_this<RegistrationThread>
|
class RegistrationThread : public std::enable_shared_from_this<RegistrationThread>
|
||||||
{
|
{
|
||||||
Connection& connection_;
|
Connection& connection_;
|
||||||
const Settings &settings_;
|
std::string password_;
|
||||||
std::shared_ptr<SelfThread> self_;
|
std::string username_;
|
||||||
|
std::string realname_;
|
||||||
|
std::string nickname_;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> caps;
|
std::unordered_map<std::string, std::string> caps;
|
||||||
std::unordered_set<std::string> outstanding;
|
std::unordered_set<std::string> outstanding;
|
||||||
@ -32,13 +32,17 @@ class RegistrationThread : public std::enable_shared_from_this<RegistrationThrea
|
|||||||
public:
|
public:
|
||||||
RegistrationThread(
|
RegistrationThread(
|
||||||
Connection& connection_,
|
Connection& connection_,
|
||||||
const Settings &,
|
std::string password,
|
||||||
std::shared_ptr<SelfThread> self
|
std::string username,
|
||||||
|
std::string realname,
|
||||||
|
std::string nickname
|
||||||
);
|
);
|
||||||
|
|
||||||
static auto start(
|
static auto start(
|
||||||
Connection& connection,
|
Connection& connection,
|
||||||
const Settings &,
|
std::string password,
|
||||||
std::shared_ptr<SelfThread> self
|
std::string username,
|
||||||
|
std::string realname,
|
||||||
|
std::string nickname
|
||||||
) -> std::shared_ptr<RegistrationThread>;
|
) -> std::shared_ptr<RegistrationThread>;
|
||||||
};
|
};
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
#include "sasl_mechanism.hpp"
|
|
||||||
|
|
||||||
auto SaslPlain::step(std::string_view msg) -> std::optional<std::string> {
|
|
||||||
if (complete_) {
|
|
||||||
return std::nullopt;
|
|
||||||
} else {
|
|
||||||
std::string reply;
|
|
||||||
|
|
||||||
reply += authzid_;
|
|
||||||
reply += '\0';
|
|
||||||
reply += authcid_;
|
|
||||||
reply += '\0';
|
|
||||||
reply += password_;
|
|
||||||
|
|
||||||
complete_ = true;
|
|
||||||
|
|
||||||
return {std::move(reply)};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <boost/signals2.hpp>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "event.hpp"
|
|
||||||
|
|
||||||
struct Connection;
|
|
||||||
|
|
||||||
class SaslMechanism
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~SaslMechanism() {}
|
|
||||||
|
|
||||||
virtual auto mechanism_name() const -> std::string = 0;
|
|
||||||
virtual auto step(std::string_view msg) -> std::optional<std::string> = 0;
|
|
||||||
virtual auto is_complete() const -> bool = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SaslPlain final : public SaslMechanism
|
|
||||||
{
|
|
||||||
std::string authcid_;
|
|
||||||
std::string authzid_;
|
|
||||||
std::string password_;
|
|
||||||
bool complete_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
SaslPlain(std::string authcid, std::string authzid, std::string password)
|
|
||||||
: authcid_{std::move(authcid)}
|
|
||||||
, authzid_{std::move(authzid)}
|
|
||||||
, password_{std::move(password)}
|
|
||||||
, complete_{false}
|
|
||||||
{}
|
|
||||||
|
|
||||||
auto mechanism_name() const -> std::string override
|
|
||||||
{
|
|
||||||
return "PLAIN";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto step(std::string_view msg) -> std::optional<std::string> override;
|
|
||||||
|
|
||||||
auto is_complete() const -> bool override
|
|
||||||
{
|
|
||||||
return complete_;
|
|
||||||
}
|
|
||||||
};
|
|
55
sasl_thread.cpp
Normal file
55
sasl_thread.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "sasl_thread.hpp"
|
||||||
|
|
||||||
|
#include <mybase64.hpp>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
#include "write_irc.hpp"
|
||||||
|
#include "irc_parse_thread.hpp"
|
||||||
|
#include "ircmsg.hpp"
|
||||||
|
|
||||||
|
auto SaslThread::start(Connection& connection) -> std::shared_ptr<SaslThread>
|
||||||
|
{
|
||||||
|
auto thread = std::make_shared<SaslThread>(connection);
|
||||||
|
|
||||||
|
connection.add_listener<IrcMsgEvent>([thread](IrcMsgEvent const& event){
|
||||||
|
if (event.command == IrcCommand::AUTHENTICATE)
|
||||||
|
{
|
||||||
|
thread->on_authenticate(event.irc.args[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SaslThread::on_authenticate(std::string_view chunk) -> void
|
||||||
|
{
|
||||||
|
if (chunk != "+") {
|
||||||
|
buffer_ += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.size() != 400)
|
||||||
|
{
|
||||||
|
std::string decoded;
|
||||||
|
decoded.resize(mybase64::decoded_size(buffer_.size()));
|
||||||
|
std::size_t len;
|
||||||
|
|
||||||
|
if (mybase64::decode(buffer_, decoded.data(), &len))
|
||||||
|
{
|
||||||
|
decoded.resize(len);
|
||||||
|
connection_.make_event<SaslMessage>(std::move(decoded));
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Invalid AUTHENTICATE base64";
|
||||||
|
send_authenticate(connection_, "*"); // abort SASL
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_.clear();
|
||||||
|
}
|
||||||
|
else if (buffer_.size() > MAX_BUFFER)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "AUTHENTICATE buffer overflow";
|
||||||
|
buffer_.clear();
|
||||||
|
send_authenticate(connection_, "*"); // abort SASL
|
||||||
|
}
|
||||||
|
}
|
89
sasl_thread.hpp
Normal file
89
sasl_thread.hpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "event.hpp"
|
||||||
|
|
||||||
|
struct Connection;
|
||||||
|
|
||||||
|
class SaslMechanism
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~SaslMechanism() {}
|
||||||
|
|
||||||
|
virtual auto mechanism_name() const -> std::string = 0;
|
||||||
|
virtual auto step(std::string_view msg) -> std::optional<std::string> = 0;
|
||||||
|
virtual auto is_complete() const -> bool = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SaslPlain final : public SaslMechanism
|
||||||
|
{
|
||||||
|
std::string authcid_;
|
||||||
|
std::string authzid_;
|
||||||
|
std::string password_;
|
||||||
|
bool complete_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SaslPlain(std::string username, std::string password)
|
||||||
|
: authcid_{std::move(username)}
|
||||||
|
, password_{std::move(password)}
|
||||||
|
, complete_{false}
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto mechanism_name() const -> std::string override
|
||||||
|
{
|
||||||
|
return "PLAIN";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto step(std::string_view msg) -> std::optional<std::string> override {
|
||||||
|
if (complete_) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
std::string reply;
|
||||||
|
|
||||||
|
reply += authzid_;
|
||||||
|
reply += '\0';
|
||||||
|
reply += authcid_;
|
||||||
|
reply += '\0';
|
||||||
|
reply += password_;
|
||||||
|
|
||||||
|
complete_ = true;
|
||||||
|
|
||||||
|
return {std::move(reply)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto is_complete() const -> bool override
|
||||||
|
{
|
||||||
|
return complete_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SaslComplete : Event
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SaslMessage : Event
|
||||||
|
{
|
||||||
|
SaslMessage(std::string message) : message{std::move(message)} {}
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SaslThread
|
||||||
|
{
|
||||||
|
Connection& connection_;
|
||||||
|
std::string buffer_;
|
||||||
|
|
||||||
|
const std::size_t MAX_BUFFER = 1024;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SaslThread(Connection& connection) : connection_{connection} {}
|
||||||
|
|
||||||
|
static auto start(Connection& connection) -> std::shared_ptr<SaslThread>;
|
||||||
|
|
||||||
|
auto on_authenticate(std::string_view) -> void;
|
||||||
|
};
|
@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
|
|
||||||
#include <mybase64.hpp>
|
|
||||||
|
|
||||||
#include <boost/container/flat_map.hpp>
|
#include <boost/container/flat_map.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
|
|
||||||
using namespace std::literals;
|
|
||||||
|
|
||||||
auto SelfThread::on_welcome(IrcMsg const& irc) -> void
|
auto SelfThread::on_welcome(IrcMsg const& irc) -> void
|
||||||
{
|
{
|
||||||
@ -91,14 +86,12 @@ auto SelfThread::on_isupport(const IrcMsg &msg) -> void
|
|||||||
for (int i = 1; i < hi; ++i)
|
for (int i = 1; i < hi; ++i)
|
||||||
{
|
{
|
||||||
auto &entry = msg.args[i];
|
auto &entry = msg.args[i];
|
||||||
|
|
||||||
// Leading minus means to stop support
|
|
||||||
if (entry.starts_with("-")) {
|
if (entry.starts_with("-")) {
|
||||||
auto const key = std::string{entry.substr(1)};
|
auto key = std::string{entry.substr(1)};
|
||||||
if (auto cursor = isupport_.find(key); cursor != isupport_.end()) {
|
if (auto cursor = isupport_.find(key); cursor != isupport_.end()) {
|
||||||
isupport_.erase(cursor);
|
isupport_.erase(cursor);
|
||||||
}
|
}
|
||||||
} else if (auto const cursor = entry.find('='); cursor != entry.npos) {
|
} else if (auto cursor = entry.find('='); cursor != entry.npos) {
|
||||||
isupport_.emplace(entry.substr(0, cursor), entry.substr(cursor+1));
|
isupport_.emplace(entry.substr(0, cursor), entry.substr(cursor+1));
|
||||||
} else {
|
} else {
|
||||||
isupport_.emplace(entry, std::string{});
|
isupport_.emplace(entry, std::string{});
|
||||||
@ -114,22 +107,18 @@ auto SelfThread::start(Connection& connection) -> std::shared_ptr<SelfThread>
|
|||||||
{
|
{
|
||||||
switch (cmd)
|
switch (cmd)
|
||||||
{
|
{
|
||||||
case IrcCommand::JOIN: thread->on_join(msg); break;
|
case IrcCommand::RPL_WELCOME: thread->on_welcome(msg); break;
|
||||||
case IrcCommand::KICK: thread->on_kick(msg); break;
|
|
||||||
case IrcCommand::MODE: thread->on_mode(msg); break;
|
|
||||||
case IrcCommand::NICK: thread->on_nick(msg); break;
|
|
||||||
case IrcCommand::PART: thread->on_part(msg); break;
|
|
||||||
case IrcCommand::RPL_ISUPPORT: thread->on_isupport(msg); break;
|
case IrcCommand::RPL_ISUPPORT: thread->on_isupport(msg); break;
|
||||||
case IrcCommand::RPL_UMODEIS: thread->on_umodeis(msg); break;
|
case IrcCommand::RPL_UMODEIS: thread->on_umodeis(msg); break;
|
||||||
case IrcCommand::RPL_WELCOME: thread->on_welcome(msg); break;
|
case IrcCommand::NICK: thread->on_nick(msg); break;
|
||||||
|
case IrcCommand::JOIN: thread->on_join(msg); break;
|
||||||
|
case IrcCommand::KICK: thread->on_kick(msg); break;
|
||||||
|
case IrcCommand::PART: thread->on_part(msg); break;
|
||||||
|
case IrcCommand::MODE: thread->on_mode(msg); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.sig_authenticate.connect([thread](auto msg) {
|
|
||||||
thread->on_authenticate(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,34 +147,3 @@ auto SelfThread::is_my_mask(std::string_view mask) const -> bool
|
|||||||
auto const bang = mask.find('!');
|
auto const bang = mask.find('!');
|
||||||
return bang != std::string_view::npos && nickname_ == mask.substr(0, bang);
|
return bang != std::string_view::npos && nickname_ == mask.substr(0, bang);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto SelfThread::on_authenticate(const std::string_view body) -> void
|
|
||||||
{
|
|
||||||
if (not sasl_mechanism_)
|
|
||||||
{
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Unexpected AUTHENTICATE from server"sv;
|
|
||||||
connection_.send_authenticate_abort();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto reply = sasl_mechanism_->step(body)) {
|
|
||||||
|
|
||||||
connection_.send_authenticate_encoded(*reply);
|
|
||||||
|
|
||||||
// Clean up completed SASL transactions
|
|
||||||
if (sasl_mechanism_->is_complete())
|
|
||||||
{
|
|
||||||
sasl_mechanism_.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto SelfThread::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void
|
|
||||||
{
|
|
||||||
if (sasl_mechanism_) {
|
|
||||||
connection_.send_authenticate("*"sv); // abort SASL
|
|
||||||
}
|
|
||||||
|
|
||||||
sasl_mechanism_ = std::move(mechanism);
|
|
||||||
connection_.send_authenticate(sasl_mechanism_->mechanism_name());
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
#include "sasl_mechanism.hpp"
|
|
||||||
|
#include <boost/container/flat_map.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@ -16,16 +17,11 @@ struct IrcMsg;
|
|||||||
class SelfThread
|
class SelfThread
|
||||||
{
|
{
|
||||||
Connection& connection_;
|
Connection& connection_;
|
||||||
|
|
||||||
std::string nickname_;
|
std::string nickname_;
|
||||||
std::string mode_;
|
std::string mode_;
|
||||||
std::unordered_set<std::string> channels_;
|
std::unordered_set<std::string> channels_;
|
||||||
|
|
||||||
// RPL_ISUPPORT state
|
|
||||||
std::unordered_map<std::string, std::string> isupport_;
|
std::unordered_map<std::string, std::string> isupport_;
|
||||||
|
|
||||||
std::unique_ptr<SaslMechanism> sasl_mechanism_;
|
|
||||||
|
|
||||||
auto on_welcome(IrcMsg const& irc) -> void;
|
auto on_welcome(IrcMsg const& irc) -> void;
|
||||||
auto on_isupport(IrcMsg const& irc) -> void;
|
auto on_isupport(IrcMsg const& irc) -> void;
|
||||||
auto on_nick(IrcMsg const& irc) -> void;
|
auto on_nick(IrcMsg const& irc) -> void;
|
||||||
@ -34,19 +30,15 @@ class SelfThread
|
|||||||
auto on_kick(IrcMsg const& irc) -> void;
|
auto on_kick(IrcMsg const& irc) -> void;
|
||||||
auto on_part(IrcMsg const& irc) -> void;
|
auto on_part(IrcMsg const& irc) -> void;
|
||||||
auto on_mode(IrcMsg const& irc) -> void;
|
auto on_mode(IrcMsg const& irc) -> void;
|
||||||
auto on_authenticate(std::string_view) -> void;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SelfThread(Connection& connection) : connection_{connection} {}
|
SelfThread(Connection& connection) : connection_{connection} {}
|
||||||
static auto start(Connection&) -> std::shared_ptr<SelfThread>;
|
static auto start(Connection&) -> std::shared_ptr<SelfThread>;
|
||||||
|
|
||||||
auto start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void;
|
|
||||||
|
|
||||||
auto get_my_nickname() const -> std::string const&;
|
auto get_my_nickname() const -> std::string const&;
|
||||||
auto get_my_mode() const -> std::string const&;
|
auto get_my_mode() const -> std::string const&;
|
||||||
auto get_my_channels() const -> std::unordered_set<std::string> const&;
|
auto get_my_channels() const -> std::unordered_set<std::string> const&;
|
||||||
|
|
||||||
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 nick) const -> bool;
|
auto is_my_mask(std::string_view nick) const -> bool;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
16
settings.cpp
16
settings.cpp
@ -7,15 +7,11 @@ auto Settings::from_stream(std::istream & in) -> Settings
|
|||||||
{
|
{
|
||||||
auto const config = toml::parse(in);
|
auto const config = toml::parse(in);
|
||||||
return Settings{
|
return Settings{
|
||||||
.host = config["host"].value_or(std::string{}),
|
.host = config["host"].value_or(std::string{"*"}),
|
||||||
.service = config["service"].value_or(std::string{}),
|
.service = config["service"].value_or(std::string{"*"}),
|
||||||
.password = config["password"].value_or(std::string{}),
|
.password = config["password"].value_or(std::string{"*"}),
|
||||||
.username = config["username"].value_or(std::string{}),
|
.username = config["username"].value_or(std::string{"*"}),
|
||||||
.realname = config["realname"].value_or(std::string{}),
|
.realname = config["realname"].value_or(std::string{"*"}),
|
||||||
.nickname = config["nickname"].value_or(std::string{}),
|
.nickname = config["nickname"].value_or(std::string{"*"})
|
||||||
.sasl_mechanism = config["sasl_mechanism"].value_or(std::string{}),
|
|
||||||
.sasl_authcid = config["sasl_authcid"].value_or(std::string{}),
|
|
||||||
.sasl_authzid = config["sasl_authzid"].value_or(std::string{}),
|
|
||||||
.sasl_password = config["sasl_password"].value_or(std::string{})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,6 @@ struct Settings
|
|||||||
std::string realname;
|
std::string realname;
|
||||||
std::string nickname;
|
std::string nickname;
|
||||||
|
|
||||||
std::string sasl_mechanism;
|
|
||||||
std::string sasl_authcid;
|
|
||||||
std::string sasl_authzid;
|
|
||||||
std::string sasl_password;
|
|
||||||
|
|
||||||
static auto from_stream(std::istream & in) -> Settings;
|
static auto from_stream(std::istream & in) -> Settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user