From c3e62d3772ca369954fbcc86604d15ce0bc50fdc Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 28 Jan 2026 19:44:49 +0000 Subject: [PATCH] Improve the usability of adding hostmasks to access lists. * When adding a user by their nickname check for their account instead of just adding their hostmask. * Allow opting out of cleaning up of malformed hostmasks. --- data/chanserv.example.conf | 6 ++++++ include/language.h | 1 + language/anope.en_US.po | 8 ++++++-- modules/chanserv/cs_access.cpp | 25 ++++++++++++++++++++----- modules/chanserv/cs_flags.cpp | 25 ++++++++++++++++++++----- modules/chanserv/cs_xop.cpp | 25 ++++++++++++++++++++----- 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/data/chanserv.example.conf b/data/chanserv.example.conf index 11be5503f..d5895a140 100644 --- a/data/chanserv.example.conf +++ b/data/chanserv.example.conf @@ -179,6 +179,12 @@ module */ disallow_channel_access = no + /* + * If set, prevents malformed hostmasks from being added to access lists + * instead of attempting to fix their format. + */ + #disallow_malformed_hostmask = yes + /* * If set, ChanServ will always lower the timestamp of registered channels to their registration date. * This prevents several race conditions where unauthorized users can join empty registered channels and set diff --git a/include/language.h b/include/language.h index 9a1c9daf5..2bbfa44b6 100644 --- a/include/language.h +++ b/include/language.h @@ -140,6 +140,7 @@ namespace Language #define CHAN_ACCESS_LIMIT N_("You can only have %u access entry on a channel.", "You can only have %u access entries on a channel.") #define CHAN_ACCESS_LIMIT_DEEP N_("You can only have %u access entry on a channel, including access entries from other channels.", "You can only have %u access entries on a channel, including access entries from other channels.") #define CHAN_ACCESS_LEVEL_RANGE _("Access level must be between %d and %d inclusive.") +#define CHAN_ACCESS_MALFORMED _("You cannot add a malformed mask to an access list. Did you mean to add %s instead?") #define CHAN_EXCEPTED _("\002%s\002 matches an except on %s and cannot be banned until the except has been removed.") #define CHAN_INFO_HEADER _("Information about channel \002%s\002:") #define CHAN_LIMIT_EXCEEDED _("You have already exceeded your limit of \002%d\002 channels.") diff --git a/language/anope.en_US.po b/language/anope.en_US.po index eaa71f5fe..5c3c5d473 100644 --- a/language/anope.en_US.po +++ b/language/anope.en_US.po @@ -16,8 +16,8 @@ msgid "" msgstr "" "Project-Id-Version: Anope\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-01-19 11:28+0000\n" -"PO-Revision-Date: 2026-01-19 11:28+0000\n" +"POT-Creation-Date: 2026-01-28 19:44+0000\n" +"PO-Revision-Date: 2026-01-28 19:44+0000\n" "Last-Translator: Sadie Powell \n" "Language-Team: English\n" "Language: en_US\n" @@ -6220,6 +6220,10 @@ msgstr "" msgid "You can't logout %s, they are a Services Operator." msgstr "" +#, c-format +msgid "You cannot add a malformed mask to an access list. Did you mean to add %s instead?" +msgstr "" + #, c-format msgid "You cannot set the %c flag." msgstr "" diff --git a/modules/chanserv/cs_access.cpp b/modules/chanserv/cs_access.cpp index e6886a0af..1ce428134 100644 --- a/modules/chanserv/cs_access.cpp +++ b/modules/chanserv/cs_access.cpp @@ -167,9 +167,10 @@ private: } } + const auto &csconf = Config->GetModule("chanserv"); if (IRCD->IsChannelValid(mask)) { - if (Config->GetModule("chanserv").Get("disallow_channel_access")) + if (csconf.Get("disallow_channel_access")) { source.Reply(_("Channels may not be on access lists.")); return; @@ -204,7 +205,7 @@ private: } else { - if (Config->GetModule("chanserv").Get("disallow_hostmask_access")) + if (csconf.Get("disallow_hostmask_access")) { source.Reply(_("Masks and unregistered users may not be on access lists.")); return; @@ -215,18 +216,32 @@ private: auto *targ = User::Find(mask, true); if (!targ) { - source.Reply(NICK_X_NOT_REGISTERED, mask.c_str()); + source.Reply(NICK_X_NOT_IN_USE, mask.c_str()); return; } - mask = "*!*@" + targ->GetDisplayedHost(); + auto *targnc = targ->Account(); + if (!targnc) + { + source.Reply(NICK_X_NOT_REGISTERED, targ->nick.c_str()); + return; + } + + mask = targnc->display; if (description.empty()) description = targ->nick; } else { // Normalize the entry mask. - mask = Entry(mask).GetCleanMask(); + const auto cleanmask = Entry(mask).GetCleanMask(); + if (csconf.Get("disallow_malformed_hostmask") && cleanmask != mask) + { + source.Reply(CHAN_ACCESS_MALFORMED, cleanmask.c_str()); + return; + } + + mask = cleanmask; } } } diff --git a/modules/chanserv/cs_flags.cpp b/modules/chanserv/cs_flags.cpp index 304599eb1..5a46b9b08 100644 --- a/modules/chanserv/cs_flags.cpp +++ b/modules/chanserv/cs_flags.cpp @@ -94,9 +94,10 @@ class CommandCSFlags final const ChanAccess *highest = u_access.Highest(); const NickAlias *na = NULL; + const auto &csconf = Config->GetModule("chanserv"); if (IRCD->IsChannelValid(mask)) { - if (Config->GetModule("chanserv").Get("disallow_channel_access")) + if (csconf.Get("disallow_channel_access")) { source.Reply(_("Channels may not be on access lists.")); return; @@ -131,7 +132,7 @@ class CommandCSFlags final } else { - if (Config->GetModule("chanserv").Get("disallow_hostmask_access")) + if (csconf.Get("disallow_hostmask_access")) { source.Reply(_("Masks and unregistered users may not be on access lists.")); return; @@ -142,18 +143,32 @@ class CommandCSFlags final auto *targ = User::Find(mask, true); if (!targ) { - source.Reply(NICK_X_NOT_REGISTERED, mask.c_str()); + source.Reply(NICK_X_NOT_IN_USE, mask.c_str()); return; } - mask = "*!*@" + targ->GetDisplayedHost(); + auto *targnc = targ->Account(); + if (!targnc) + { + source.Reply(NICK_X_NOT_REGISTERED, targ->nick.c_str()); + return; + } + + mask = targnc->display; if (description.empty()) description = targ->nick; } else { // Normalize the entry mask. - mask = Entry(mask).GetCleanMask(); + const auto cleanmask = Entry(mask).GetCleanMask(); + if (csconf.Get("disallow_malformed_hostmask") && cleanmask != mask) + { + source.Reply(CHAN_ACCESS_MALFORMED, cleanmask.c_str()); + return; + } + + mask = cleanmask; } } } diff --git a/modules/chanserv/cs_xop.cpp b/modules/chanserv/cs_xop.cpp index 69e02cc4d..5d2f5e98d 100644 --- a/modules/chanserv/cs_xop.cpp +++ b/modules/chanserv/cs_xop.cpp @@ -142,9 +142,10 @@ private: } } + const auto &csconf = Config->GetModule("chanserv"); if (IRCD->IsChannelValid(mask)) { - if (Config->GetModule("chanserv").Get("disallow_channel_access")) + if (csconf.Get("disallow_channel_access")) { source.Reply(_("Channels may not be on access lists.")); return; @@ -179,7 +180,7 @@ private: } else { - if (Config->GetModule("chanserv").Get("disallow_hostmask_access")) + if (csconf.Get("disallow_hostmask_access")) { source.Reply(_("Masks and unregistered users may not be on access lists.")); return; @@ -190,18 +191,32 @@ private: auto *targ = User::Find(mask, true); if (!targ) { - source.Reply(NICK_X_NOT_REGISTERED, mask.c_str()); + source.Reply(NICK_X_NOT_IN_USE, mask.c_str()); return; } - mask = "*!*@" + targ->GetDisplayedHost(); + auto *targnc = targ->Account(); + if (!targnc) + { + source.Reply(NICK_X_NOT_REGISTERED, targ->nick.c_str()); + return; + } + + mask = targnc->display; if (description.empty()) description = targ->nick; } else { // Normalize the entry mask. - mask = Entry(mask).GetCleanMask(); + const auto cleanmask = Entry(mask).GetCleanMask(); + if (csconf.Get("disallow_malformed_hostmask") && cleanmask != mask) + { + source.Reply(CHAN_ACCESS_MALFORMED, cleanmask.c_str()); + return; + } + + mask = cleanmask; } } }