1
0
mirror of https://github.com/anope/anope.git synced 2026-06-12 18:54:47 +02:00
Files
2026-03-26 16:32:56 +00:00

286 lines
8.2 KiB
C++

// Anope IRC Services <https://www.anope.org/>
//
// Copyright (C) 2003-2026 Anope Contributors
//
// Anope is free software. You can use, modify, and/or distribute it under the
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
// for the complete terms of this license and docs/AUTHORS.txt for a list of
// contributors.
//
// Based on the original code of Epona by Lara
// Based on the original code of Services by Andy Church
//
// SPDX-License-Identifier: GPL-2.0-only
#include "module.h"
#include "modules/operserv/session.h"
struct Stats final
: Serializable
{
static Stats *me;
Stats() : Serializable("Stats")
{
if (!me)
me = this;
}
};
struct StatsType final
: Serialize::Type
{
StatsType()
: Serialize::Type("Stats")
{
}
void Serialize(Serializable *obj, Serialize::Data &data) const override
{
data.Store("maxusercnt", MaxUserCount);
data.Store("maxusertime", MaxUserTime);
}
Serializable *Unserialize(Serializable *obj, Serialize::Data &data) const override
{
MaxUserCount = data.Load<size_t>("maxusercnt");
MaxUserTime = data.Load<time_t>("maxusertime");
return Stats::me;
}
};
Stats *Stats::me = nullptr;
class CommandOSStats final
: public Command
{
private:
ServiceReference<XLineManager> akills, snlines, sqlines;
static void ReportXLineStats(CommandSource &source, XLineManager *xlm, const char *type, const char *config)
{
source.Reply(_("Current number of %ss: \002%zu\002"), type, xlm->GetCount());
const auto timeout = Config->GetModule("operserv").Get<time_t>(config, "30d");
if (timeout)
source.Reply(_("Default %s expiry time: \002%s\002"), type, Anope::Duration(timeout, source.nc, true).c_str());
else
source.Reply(_("Default %s expiry time: \002No expiration\002"), type);
}
private:
void DoStatsAkill(CommandSource &source)
{
if (akills)
ReportXLineStats(source, *akills, "AKILL", "autokillexpiry");
if (snlines)
ReportXLineStats(source, *snlines, "SNLINE", "snlineexpiry");
if (sqlines)
ReportXLineStats(source, *sqlines, "SQLINE", "sqlineexpiry");
}
static void DoStatsReset(CommandSource &source)
{
MaxUserCount = UserListByNick.size();
MaxUserTime = Anope::CurTime;
Stats::me->QueueUpdate();
source.Reply(_("Statistics reset."));
}
static void DoStatsUptime(CommandSource &source)
{
time_t uptime = Anope::CurTime - Anope::StartTime;
source.Reply(_("Current users: \002%zu\002 (\002%zu\002 ops)"), UserListByNick.size(), OperCount);
source.Reply(_("Maximum users: \002%zu\002 (%s)"), MaxUserCount, Anope::strftime(MaxUserTime, source.GetAccount()).c_str());
source.Reply(_("Services up %s."), Anope::Duration(uptime, source.GetAccount()).c_str());
}
static void DoStatsUplink(CommandSource &source)
{
Anope::string buf;
for (const auto &capab : Servers::Capab)
buf += " " + capab;
if (!buf.empty())
buf.erase(buf.begin());
source.Reply(_("Uplink server: %s"), Me->GetLinks().front()->GetName().c_str());
source.Reply(_("Uplink capab: %s"), buf.c_str());
source.Reply(_("Servers found: %zu"), Servers::ByName.size() - 1);
}
template<typename T> void GetHashStats(const T &map, size_t &entries, size_t &buckets, size_t &max_chain)
{
entries = map.size(), buckets = map.bucket_count(), max_chain = 0;
for (size_t i = 0; i < buckets; ++i)
if (map.bucket_size(i) > max_chain)
max_chain = map.bucket_size(i);
}
void DoStatsHash(CommandSource &source)
{
size_t entries, buckets, max_chain;
GetHashStats(UserListByNick, entries, buckets, max_chain);
source.Reply(_("Users (nick): %lu entries, %lu buckets, longest chain is %zu"), entries, buckets, max_chain);
if (!UserListByUID.empty())
{
GetHashStats(UserListByUID, entries, buckets, max_chain);
source.Reply(_("Users (uid): %lu entries, %lu buckets, longest chain is %zu"), entries, buckets, max_chain);
}
GetHashStats(ChannelList, entries, buckets, max_chain);
source.Reply(_("Channels: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
GetHashStats(*RegisteredChannelList, entries, buckets, max_chain);
source.Reply(_("Registered channels: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
GetHashStats(*NickAliasList, entries, buckets, max_chain);
source.Reply(_("Registered nicknames: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
GetHashStats(*NickCoreList, entries, buckets, max_chain);
source.Reply(_("Registered accounts: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
if (OperServ::session_service)
{
GetHashStats(OperServ::session_service->GetSessions(), entries, buckets, max_chain);
source.Reply(_("Sessions: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
}
}
void DoStatsPassword(CommandSource &source)
{
Anope::map<size_t> counts;
size_t missing = 0;
size_t unknown = 0;
for (const auto &[_, nc] : *NickCoreList)
{
if (nc->pass.empty())
{
missing++;
continue;
}
auto sep = nc->pass.find(':');
if (sep == Anope::string::npos)
{
unknown++;
continue;
}
counts[nc->pass.substr(0, sep)]++;
}
for (const auto &[algo, count] : counts)
source.Reply(_("Passwords encrypted with %s: %zu"), algo.c_str(), count);
if (missing)
source.Reply(_("Missing passwords: %zu"), missing);
if (unknown)
source.Reply(_("Unknown passwords: %zu"), unknown);
}
public:
CommandOSStats(Module *creator) : Command(creator, "operserv/stats", 0, 1),
akills("XLineManager", "xlinemanager/sgline"), snlines("XLineManager", "xlinemanager/snline"), sqlines("XLineManager", "xlinemanager/sqline")
{
this->SetDesc(_("Show status of services and network"));
this->SetSyntax("[AKILL | HASH | PASSWORD | UPLINK | UPTIME | ALL | RESET]");
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
Anope::string extra = !params.empty() ? params[0] : "";
Log(LOG_ADMIN, source, this) << extra;
if (extra.equals_ci("RESET"))
return this->DoStatsReset(source);
bool handled = false;
if (extra.equals_ci("ALL") || extra.equals_ci("AKILL"))
{
this->DoStatsAkill(source);
handled = true;
}
if (extra.equals_ci("ALL") || extra.equals_ci("HASH"))
{
this->DoStatsHash(source);
handled = true;
}
if (extra.equals_ci("ALL") || extra.equals_ci("PASSWORD"))
{
this->DoStatsPassword(source);
handled = true;
}
if (extra.equals_ci("ALL") || extra.equals_ci("UPLINK"))
{
this->DoStatsUplink(source);
handled = true;
}
if (extra.empty() || extra.equals_ci("ALL") || extra.equals_ci("UPTIME"))
{
this->DoStatsUptime(source);
handled = true;
}
if (!handled)
source.Reply(_("Unknown STATS option: \002%s\002"), extra.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Without any option, shows the current number of users online, "
"and the highest number of users online since services was "
"started, and the length of time services has been running."
"\n\n"
"With the \002AKILL\002 option, displays the current size of the "
"AKILL list and the current default expiry time."
"\n\n"
"The \002RESET\002 option currently resets the maximum user count "
"to the number of users currently present on the network."
"\n\n"
"The \002PASSWORD\002 option displays the encryption algorithms used "
"for user passwords. "
"\n\n"
"The \002UPLINK\002 option displays information about the current "
"server Anope uses as an uplink to the network."
"\n\n"
"The \002HASH\002 option displays information about the hash maps."
"\n\n"
"The \002ALL\002 option displays all of the above statistics."
));
return true;
}
};
class OSStats final
: public Module
{
CommandOSStats commandosstats;
StatsType stats_type;
Stats stats_saver;
public:
OSStats(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandosstats(this)
{
}
void OnUserConnect(User *u, bool &exempt) override
{
if (UserListByNick.size() == MaxUserCount && Anope::CurTime == MaxUserTime)
Stats::me->QueueUpdate();
}
};
MODULE_INIT(OSStats)