xbot/myirc/snote.cpp

281 lines
8.2 KiB
C++
Raw Normal View History

2025-01-23 21:23:32 -08:00
#include "snote.hpp"
2023-11-25 20:09:20 -08:00
2023-11-27 18:47:32 -08:00
#include "c_callback.hpp"
2023-11-25 20:09:20 -08:00
2023-11-27 19:09:45 -08:00
#include <hs.h>
2023-11-29 13:13:48 -08:00
#include <boost/log/trivial.hpp>
2023-11-26 21:16:56 -08:00
#include <cstdlib>
2023-11-27 19:09:45 -08:00
#include <cstring>
2025-01-23 21:23:32 -08:00
#include <optional>
2023-11-27 19:09:45 -08:00
#include <regex>
2023-11-26 21:16:56 -08:00
#include <stdexcept>
#include <utility>
2023-11-25 20:09:20 -08:00
2023-11-26 21:16:56 -08:00
namespace {
2023-11-25 20:09:20 -08:00
2023-11-26 21:16:56 -08:00
struct SnotePattern
2023-11-25 20:09:20 -08:00
{
2025-01-29 20:43:03 -08:00
SnotePattern(SnoteTag tag, const char *expression)
2025-01-25 15:45:31 -08:00
: tag{tag}
, expression{expression}
, regex{expression, std::regex_constants::ECMAScript | std::regex_constants::optimize}
2023-11-26 21:16:56 -08:00
{
}
SnoteTag tag;
2025-01-25 15:45:31 -08:00
const char *expression;
2023-11-26 21:16:56 -08:00
std::regex regex;
};
2025-01-29 20:43:03 -08:00
using namespace std::literals;
2025-01-25 15:45:31 -08:00
const SnotePattern static patterns[] = {
2023-11-26 21:16:56 -08:00
{SnoteTag::ClientConnecting,
R"(^Client connecting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \{([^ ]*)\} <([^ ]*)> \[(.*)\]$)"},
{SnoteTag::ClientExiting,
R"(^Client exiting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \[(.*)\]$)"},
2023-11-27 14:12:20 -08:00
{SnoteTag::RejectingKlined,
R"(^Rejecting K-Lined user ([^ ]+)\[([^@]+)@([^\]]+)\] \[([^\] ]+)\] \((.*)\)$)"},
{SnoteTag::NickChange,
R"(^Nick change: From ([^ ]+) to ([^ ]+) \[([^@]+)@([^ ]+)\]$)"},
{SnoteTag::CreateChannel,
R"(^([^ ]+) is creating new channel ([^ ]+)$)"},
{SnoteTag::TemporaryKlineExpired,
R"(^Temporary K-line for \[([^ ]+)\] expired$)"},
2023-11-27 18:47:32 -08:00
{SnoteTag::PropagatedBanExpired,
R"(^Propagated ban for \[([^ ]+)\] expired$)"},
2023-11-27 14:12:20 -08:00
{SnoteTag::DisconnectingKlined,
R"(^Disconnecting K-Lined user ([^ ]+)\[([^@]+)@([^ ]+)\] \((.*)\)$)"},
2023-11-27 18:47:32 -08:00
{SnoteTag::NewPropagatedKline,
R"(^([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} added global ([^ ]+) min\. K-Line for \[([^ ]+)\] \[(.*)\]$)"},
{SnoteTag::NewTemporaryKline,
R"(^([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} added temporary ([^ ]+) min\. K-Line for \[([^ ]+)\] \[(.*)\]$)"},
{SnoteTag::LoginAttempts,
"^Warning: \x02([^ ]+)\x02 failed login attempts to \x02([^ ]+)\x02\\. Last attempt received from \x02(.+)\x02.*$"},
{SnoteTag::PossibleFlooder,
R"(^Possible Flooder ([^ ]+)\[([^ ]+)@[^ ]+\] on ([^ ]+) target: ([^ ]+)$)"},
2025-01-30 08:27:57 -08:00
{SnoteTag::KilledRemote,
2023-11-27 18:47:32 -08:00
R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) Path: ([^ ]+) \((.*)\)$)"},
2023-11-29 13:54:34 -08:00
2025-01-30 08:27:57 -08:00
{SnoteTag::KilledRemoteOper,
R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) Path: ([^ ]+)!([^ ]+)!([^ ]+)!([^ ]+) (.*)$)"},
2025-01-30 09:28:28 -08:00
2025-01-30 08:27:57 -08:00
{SnoteTag::Killed,
R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) (.*)$)"},
2023-11-29 13:54:34 -08:00
{SnoteTag::TooManyGlobalConnections,
R"(^Too many global connections for ([^ ]+)\[([^ ]+)@([^ ]+)\] \[(.*)\]$)"},
2025-01-22 23:49:48 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::TooManyUserConnections,
R"(^Too many user connections for ([^ ]+)\[([^ ]+)@([^ ]+)\] \[(.*)\]$)"},
2023-11-29 14:52:57 -08:00
{SnoteTag::SetVhostOnMarkedAccount,
"^\x02([^ ]+)\x02 set vhost ([^ ]+) on the \x02MARKED\x02 account ([^ ]+).$"},
2025-01-28 20:01:51 -08:00
{SnoteTag::IsNowOper,
R"(^([^ ]+) \(([^ ]+)!([^ ]+)@([^ ]+)\) is now an operator$)"},
2025-01-30 12:55:40 -08:00
{SnoteTag::IsNowOperGlobal,
R"(^([^ ]+) \(([^ ]+)@([^ ]+)\) is now an operator$)"},
2025-01-28 21:43:44 -08:00
{SnoteTag::OperspyWhois,
2025-01-30 09:28:28 -08:00
R"(^OPERSPY ([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} WHOIS ([^ ]+)!([^ ]+)@([^ ]+) ([^ ]+) $)"}, // trailing space intentional
2025-01-28 21:43:44 -08:00
2025-01-30 12:55:40 -08:00
{SnoteTag::OperspyWho,
R"(^OPERSPY ([^ ]+)!([^ ]+)@([^ ]+)\{([^ ]+)\} WHO ([^ ]+)$)"},
2025-01-28 21:43:44 -08:00
{SnoteTag::Freeze,
2025-01-29 20:43:03 -08:00
"^\x02([^ ]+)\x02 froze the account \x02([^ ]+)\x02 \\((.*)\\)\\.$"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::DroppedChannel,
"^\x02([^ ]+)\x02 dropped the channel \x02([^ ]+)\x02$"},
2025-01-28 22:54:55 -08:00
2025-01-30 13:36:18 -08:00
{SnoteTag::DroppedAccount,
"^\x02([^ ]+)\x02 dropped the account \x02([^ ]+)\x02$"},
{SnoteTag::DroppedNick,
"^\x02([^ ]+)\x02 dropped the nick \x02([^ ]+)\x02 from ([^ ]+)$"},
{SnoteTag::DroppedNickRename,
"^\x02([^ ]+)\x02 dropped the nick \x02([^ ]+)\x02 from ([^ ]+), changing account name to \x02([^ ]+)\x02$"},
2025-01-28 22:54:55 -08:00
{SnoteTag::Spambot,
2025-01-29 20:43:03 -08:00
R"(^User ([^ ]+) \(([^ ]+)@([^ ]+)\) trying to join ([^ ]+) is a possible spambot$)"},
{SnoteTag::SaveMessage,
R"(^Received SAVE message for ([^ ]+) from ([^ ]+)$)"},
{SnoteTag::NickCollisionServices,
R"(^Nick collision due to services forced nick change on ([^ ]+)$)"},
{SnoteTag::NickCollision,
2025-01-30 12:55:40 -08:00
R"(^Nick collision on ([^ ]+)\(([^ ]+) <- ([^ ]+)\)\(([^ ]+) ([^ ]+)\)$)"},
2025-01-29 20:43:03 -08:00
{SnoteTag::TemporaryDline,
R"(^([^ ]+) added temporary ([^ ]+) min\. D-Line for \[([^ ]+)\] \[(.*)\]$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallengeMissingSecure,
R"(^Failed CHALLENGE attempt - missing secure connection by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallenge,
R"(^Failed CHALLENGE attempt by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallengeHostMismatch,
R"(^Failed CHALLENGE attempt - host mismatch by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallengeNoBlock,
R"(^Failed CHALLENGE attempt - user@host mismatch or no operator block for ([^ ]+) by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallengeTls,
R"(^Failed CHALLENGE attempt - missing SSL/TLS by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
2025-01-30 09:28:28 -08:00
2025-01-29 20:43:03 -08:00
{SnoteTag::FailedChallengeFingerprintMismatch,
2025-01-30 08:27:57 -08:00
R"(^Failed CHALLENGE attempt - client certificate fingerprint mismatch by ([^ ]+) \(([^ ]+)@([^ ]+)\)$)"},
{SnoteTag::SighupReloadingConf,
R"(^Got signal SIGHUP, reloading ircd conf\. file$)"},
2025-01-30 09:28:28 -08:00
2025-01-30 08:27:57 -08:00
{SnoteTag::JoinedJuped,
R"(^User ([^ ]+) \(([^ ]+)@([^ ]+)\) is attempting to join locally juped channel ([^ ]+) \((.*)\)$)"},
2023-11-26 21:16:56 -08:00
};
2025-01-25 15:45:31 -08:00
static auto setup_database() -> hs_database_t *
2023-11-26 21:16:56 -08:00
{
2025-01-25 15:45:31 -08:00
const auto n = std::size(patterns);
std::vector<const char *> expressions;
2023-11-27 18:47:32 -08:00
std::vector<unsigned> flags(n, HS_FLAG_SINGLEMATCH);
2023-11-26 21:16:56 -08:00
std::vector<unsigned> ids;
2023-11-27 18:47:32 -08:00
expressions.reserve(n);
ids.reserve(n);
2023-11-26 21:16:56 -08:00
2023-11-27 14:12:20 -08:00
for (std::size_t i = 0; i < n; i++)
2023-11-26 21:16:56 -08:00
{
2023-11-27 14:12:20 -08:00
expressions.push_back(patterns[i].expression);
ids.push_back(i);
2023-11-26 21:16:56 -08:00
}
2025-01-25 15:45:31 -08:00
hs_database_t *db;
2023-11-26 21:16:56 -08:00
hs_compile_error *error;
2023-11-27 14:12:20 -08:00
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))
2023-11-26 21:16:56 -08:00
{
2025-01-25 15:45:31 -08:00
case HS_COMPILER_ERROR: {
2025-01-30 08:27:57 -08:00
std::string msg = std::to_string(error->expression) + ": " + error->message;
2025-01-25 15:45:31 -08:00
hs_free_compile_error(error);
throw std::runtime_error{std::move(msg)};
}
case HS_SUCCESS:
break;
default:
abort();
2023-11-26 21:16:56 -08:00
}
2023-11-27 14:12:20 -08:00
return db;
2023-11-26 21:16:56 -08:00
}
} // namespace
2025-01-24 14:48:15 -08:00
SnoteCore::SnoteCore()
2023-11-26 21:16:56 -08:00
{
2025-01-23 21:23:32 -08:00
db_.reset(setup_database());
2023-11-26 21:16:56 -08:00
2025-01-25 15:45:31 -08:00
hs_scratch_t *scratch = nullptr;
2025-01-23 21:23:32 -08:00
if (HS_SUCCESS != hs_alloc_scratch(db_.get(), &scratch))
2023-11-26 21:16:56 -08:00
{
abort();
}
2025-01-23 21:23:32 -08:00
scratch_.reset(scratch);
}
2023-11-26 21:16:56 -08:00
2025-01-24 14:48:15 -08:00
auto SnoteCore::match(const IrcMsg &msg) -> std::optional<SnoteMatch>
2025-01-23 21:23:32 -08:00
{
2025-01-25 15:45:31 -08:00
static const char *const prefix = "*** Notice -- ";
2025-01-23 21:23:32 -08:00
2025-01-25 15:45:31 -08:00
auto &args = msg.args;
if ("*" != args[0] || !args[1].starts_with(prefix))
{
2025-01-23 21:23:32 -08:00
return std::nullopt;
}
2025-01-25 15:45:31 -08:00
const auto message = args[1].substr(strlen(prefix));
2025-01-23 21:23:32 -08:00
unsigned match_id;
2025-01-25 15:45:31 -08:00
auto cb = [&match_id](unsigned id, unsigned long long, unsigned long long, unsigned) -> int {
2025-01-23 21:23:32 -08:00
match_id = id;
return 1; // stop scanning
};
2025-01-25 15:45:31 -08:00
const auto scan_result = hs_scan(
db_.get(),
message.data(), message.size(),
0, // no flags
scratch_.get(),
CCallback<decltype(cb)>::invoke, &cb
);
2025-01-23 21:23:32 -08:00
switch (scan_result)
{
case HS_SUCCESS:
BOOST_LOG_TRIVIAL(warning) << "Unknown snote: " << message;
return std::nullopt;
2025-01-25 15:45:31 -08:00
case HS_SCAN_TERMINATED: {
auto &pattern = patterns[match_id];
2025-01-24 14:48:15 -08:00
return SnoteMatch{pattern.tag, pattern.regex, message};
2025-01-23 21:23:32 -08:00
}
2023-11-26 21:16:56 -08:00
2025-01-23 21:23:32 -08:00
default:
abort();
}
2023-11-25 20:09:20 -08:00
}
2023-11-27 19:09:45 -08:00
2025-01-25 15:45:31 -08:00
auto SnoteMatch::get_results() -> const std::match_results<std::string_view::const_iterator> &
2023-11-28 11:34:27 -08:00
{
2025-01-25 15:45:31 -08:00
if (auto results = std::get_if<1>(&components_))
{
2023-11-28 11:34:27 -08:00
return *results;
}
2025-01-24 14:48:15 -08:00
auto [regex, message] = std::get<0>(components_);
2025-01-25 15:45:31 -08:00
auto &results = components_.emplace<1>();
2023-11-28 11:34:27 -08:00
if (not std::regex_match(message.begin(), message.end(), results, regex))
{
// something went wrong - hyperscan disagrees with std::regex
abort();
}
return results;
}
2025-01-25 15:45:31 -08:00
auto SnoteCore::DbDeleter::operator()(hs_database_t *db) const -> void
2023-11-27 19:09:45 -08:00
{
if (HS_SUCCESS != hs_free_database(db))
{
abort();
}
}
2025-01-25 15:45:31 -08:00
auto SnoteCore::ScratchDeleter::operator()(hs_scratch_t *scratch) const -> void
2023-11-27 19:09:45 -08:00
{
if (HS_SUCCESS != hs_free_scratch(scratch))
{
abort();
}
}
2025-01-23 21:23:32 -08:00
SnoteCore snoteCore;