#include "snote.hpp" #include "c_callback.hpp" #include #include #include #include #include #include #include #include namespace { struct SnotePattern { SnotePattern(SnoteTag tag, const char *expression, unsigned flags = 0) : tag{tag} , expression{expression} , regex{expression, std::regex_constants::ECMAScript | std::regex_constants::optimize} { } SnoteTag tag; const char *expression; std::regex regex; }; const SnotePattern static patterns[] = { {SnoteTag::ClientConnecting, R"(^Client connecting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \{([^ ]*)\} <([^ ]*)> \[(.*)\]$)"}, {SnoteTag::ClientExiting, R"(^Client exiting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \[(.*)\]$)"}, {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$)"}, {SnoteTag::PropagatedBanExpired, R"(^Propagated ban for \[([^ ]+)\] expired$)"}, {SnoteTag::DisconnectingKlined, R"(^Disconnecting K-Lined user ([^ ]+)\[([^@]+)@([^ ]+)\] \((.*)\)$)"}, {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: ([^ ]+)$)"}, {SnoteTag::Killed, R"(^Received KILL message for ([^ ]+)!([^ ]+)@([^ ]+)\. From ([^ ]+) Path: ([^ ]+) \((.*)\)$)"}, {SnoteTag::TooManyGlobalConnections, R"(^Too many global connections for ([^ ]+)\[([^ ]+)@([^ ]+)\] \[(.*)\]$)"}, {SnoteTag::SetVhostOnMarkedAccount, "^\x02([^ ]+)\x02 set vhost ([^ ]+) on the \x02MARKED\x02 account ([^ ]+).$"}, {SnoteTag::IsNowOper, R"(^([^ ]+) \(([^ ]+)!([^ ]+)@([^ ]+)\) is now an operator$)"}, {SnoteTag::NickCollision, R"(^Nick collision due to services forced nick change on ([^ ]+)$)"}, }; static auto setup_database() -> hs_database_t * { const auto n = std::size(patterns); std::vector expressions; std::vector flags(n, HS_FLAG_SINGLEMATCH); std::vector ids; expressions.reserve(n); ids.reserve(n); for (std::size_t i = 0; i < n; i++) { expressions.push_back(patterns[i].expression); ids.push_back(i); } hs_database_t *db; hs_compile_error *error; 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)) { case HS_COMPILER_ERROR: { std::string msg = error->message; hs_free_compile_error(error); throw std::runtime_error{std::move(msg)}; } case HS_SUCCESS: break; default: abort(); } return db; } } // namespace SnoteCore::SnoteCore() { db_.reset(setup_database()); hs_scratch_t *scratch = nullptr; if (HS_SUCCESS != hs_alloc_scratch(db_.get(), &scratch)) { abort(); } scratch_.reset(scratch); } auto SnoteCore::match(const IrcMsg &msg) -> std::optional { static const char *const prefix = "*** Notice -- "; auto &args = msg.args; if ("*" != args[0] || !args[1].starts_with(prefix)) { return std::nullopt; } const auto message = args[1].substr(strlen(prefix)); unsigned match_id; auto cb = [&match_id](unsigned id, unsigned long long, unsigned long long, unsigned) -> int { match_id = id; return 1; // stop scanning }; const auto scan_result = hs_scan( db_.get(), message.data(), message.size(), 0, // no flags scratch_.get(), CCallback::invoke, &cb ); switch (scan_result) { case HS_SUCCESS: BOOST_LOG_TRIVIAL(warning) << "Unknown snote: " << message; return std::nullopt; case HS_SCAN_TERMINATED: { auto &pattern = patterns[match_id]; return SnoteMatch{pattern.tag, pattern.regex, message}; } default: abort(); } } auto SnoteMatch::get_results() -> const std::match_results & { if (auto results = std::get_if<1>(&components_)) { return *results; } auto [regex, message] = std::get<0>(components_); auto &results = components_.emplace<1>(); if (not std::regex_match(message.begin(), message.end(), results, regex)) { // something went wrong - hyperscan disagrees with std::regex abort(); } return results; } auto SnoteCore::DbDeleter::operator()(hs_database_t *db) const -> void { if (HS_SUCCESS != hs_free_database(db)) { abort(); } } auto SnoteCore::ScratchDeleter::operator()(hs_scratch_t *scratch) const -> void { if (HS_SUCCESS != hs_free_scratch(scratch)) { abort(); } } SnoteCore snoteCore;