From 44b7493eb120be3ad4b79c599836dfb505547488 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Sat, 13 Jun 2026 23:42:30 +0100 Subject: [PATCH] Add support for HostServ request cooldowns. --- data/hostserv.example.conf | 13 +++++++ modules/hostserv/hs_request.cpp | 62 +++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/data/hostserv.example.conf b/data/hostserv.example.conf index e6ba2e13b..fb01cf3d1 100644 --- a/data/hostserv.example.conf +++ b/data/hostserv.example.conf @@ -201,6 +201,19 @@ module { name = "hs_request" + + /* + * How long after a requested vhost is activated does a user have to wait + * before they can request a new vhost. Defaults to 24 hours. + */ + #activationcooldown = 24h + + /* + * How long after a requested vhost is rejected does a user have to wait + * before they can request a new vhost. Defaults to 24 hours. + */ + #rejectioncooldown = 24h + /* * If set, Anope will send a memo to the user requesting a vhost when it's been * approved or rejected. diff --git a/modules/hostserv/hs_request.cpp b/modules/hostserv/hs_request.cpp index 877763aba..b004de4f8 100644 --- a/modules/hostserv/hs_request.cpp +++ b/modules/hostserv/hs_request.cpp @@ -183,8 +183,13 @@ public: class CommandHSRequest final : public Command { +private: + SerializableExtensibleItem &requestcooldown; + public: - CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1) + CommandHSRequest(Module *creator, SerializableExtensibleItem &ext) + : Command(creator, "hostserv/request", 1, 1) + , requestcooldown(ext) { this->SetDesc(_("Request a vhost for your nick")); this->SetSyntax(_("vhost")); @@ -263,12 +268,25 @@ public: return; } - time_t send_delay = Config->GetModule("memoserv").Get("senddelay"); - if (Config->GetModule(this->owner).Get("memooper") && send_delay > 0 && u && u->lastmemosend + send_delay > Anope::CurTime) + time_t waituntil = 0; { - auto waitperiod = (u->lastmemosend + send_delay) - Anope::CurTime; + // Check whether the user is on a request cooldown. + const auto *last_req = requestcooldown.Get(na); + if (last_req) + waituntil = *last_req; + } + if (Config->GetModule(this->owner).Get("memooper")) + { + // Check whether the user can send a memo to opers yet. + const auto send_delay = Config->GetModule("memoserv").Get("senddelay"); + if (send_delay > 0 && u && u->lastmemosend) + waituntil = std::max(waituntil, u->lastmemosend + send_delay); + } + + if (waituntil && waituntil > Anope::CurTime) + { + const auto waitperiod = waituntil - Anope::CurTime; source.Reply(_("Please wait %s before requesting a new vhost."), Anope::Duration(waitperiod, source.GetAccount()).c_str()); - u->lastmemosend = Anope::CurTime; return; } @@ -319,8 +337,15 @@ public: class CommandHSActivate final : public Command { +private: + SerializableExtensibleItem &requestcooldown; + public: - CommandHSActivate(Module *creator) : Command(creator, "hostserv/activate", 1, 1) + time_t cooldown; + + CommandHSActivate(Module *creator, SerializableExtensibleItem &ext) + : Command(creator, "hostserv/activate", 1, 1) + , requestcooldown(ext) { this->SetDesc(_("Approve the requested vhost of a user")); this->SetSyntax(_("\037nick\037")); @@ -348,6 +373,8 @@ public: source.Reply(_("VHost for %s has been activated."), na->nick.c_str()); Log(LOG_COMMAND, source, this) << "for " << na->nick << " for vhost " << (!req->ident.empty() ? req->ident + "@" : "") << req->host; + + requestcooldown.Set(na, Anope::CurTime + cooldown); na->Shrink(HOSTSERV_HOST_REQUEST_EXT); } else @@ -369,8 +396,15 @@ public: class CommandHSReject final : public Command { +private: + SerializableExtensibleItem &requestcooldown; + public: - CommandHSReject(Module *creator) : Command(creator, "hostserv/reject", 1, 2) + time_t cooldown; + + CommandHSReject(Module *creator, SerializableExtensibleItem &ext) + : Command(creator, "hostserv/reject", 1, 2) + , requestcooldown(ext) { this->SetDesc(_("Reject the requested vhost of a user")); this->SetSyntax(_("\037nick\037 [\037reason\037]")); @@ -391,6 +425,7 @@ public: auto *req = HostRequestImpl::Get(na); if (req) { + requestcooldown.Set(na, Anope::CurTime + cooldown); na->Shrink(HOSTSERV_HOST_REQUEST_EXT); if (Config->GetModule(this->owner).Get("memouser") && MemoServ::service) @@ -553,8 +588,10 @@ public: class HSRequest final : public Module { +private: + SerializableExtensibleItem requestcooldown; CommandHSRequest commandhsrequest; - CommandHSActivate commandhsactive; + CommandHSActivate commandhsactivate; CommandHSReject commandhsreject; CommandHSWaiting commandhswaiting; CommandHSValidate commandhsvalidate; @@ -564,9 +601,10 @@ class HSRequest final public: HSRequest(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsrequest(this) - , commandhsactive(this) - , commandhsreject(this) + , requestcooldown(this, "HS_REQUEST_COOLDOWN") + , commandhsrequest(this, requestcooldown) + , commandhsactivate(this, requestcooldown) + , commandhsreject(this, requestcooldown) , commandhswaiting(this) , commandhsvalidate(this) , hostrequest(this, HOSTSERV_HOST_REQUEST_EXT) @@ -578,6 +616,8 @@ public: void OnReload(Configuration::Conf &conf) override { const auto &block = conf.GetModule(this); + commandhsactivate.cooldown = block.Get("activationcooldown", "24h"); + commandhsreject.cooldown = block.Get("rejectioncooldown", "24h"); commandhsvalidate.cooldown = block.Get("validationcooldown", "5m"); validation_record = block.Get("validationrecord", "anope-dns-validation"); }