summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanieldg <danieldg@e03df62e-2008-0410-955e-edbf42e46eb7>2009-10-12 18:12:48 +0000
committerdanieldg <danieldg@e03df62e-2008-0410-955e-edbf42e46eb7>2009-10-12 18:12:48 +0000
commit579e707f017ca237d260165992f0b72bafba152c (patch)
tree3e3b6d77b90b505def180fec1dc3c040a00a4871
parent480a798dbdafca245403ad357f5dba2aa4f73261 (diff)
m_exemptchanops by jackmcbarn - channel mode +X to allow per-channel setting
git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@11855 e03df62e-2008-0410-955e-edbf42e46eb7
-rw-r--r--conf/inspircd.conf.example4
-rw-r--r--conf/modules.conf.example15
-rw-r--r--include/configreader.h12
-rw-r--r--include/modules.h8
-rw-r--r--src/configreader.cpp9
-rw-r--r--src/modules.cpp1
-rw-r--r--src/modules/m_blockcaps.cpp8
-rw-r--r--src/modules/m_blockcolor.cpp6
-rw-r--r--src/modules/m_censor.cpp7
-rw-r--r--src/modules/m_chanfilter.cpp5
-rw-r--r--src/modules/m_exemptchanops.cpp130
-rw-r--r--src/modules/m_messageflood.cpp6
-rw-r--r--src/modules/m_nickflood.cpp8
-rw-r--r--src/modules/m_noctcp.cpp6
-rw-r--r--src/modules/m_nonicks.cpp5
-rw-r--r--src/modules/m_nonotice.cpp7
-rw-r--r--src/modules/m_services_account.cpp4
-rw-r--r--src/modules/m_stripcolor.cpp8
18 files changed, 193 insertions, 56 deletions
diff --git a/conf/inspircd.conf.example b/conf/inspircd.conf.example
index cfdba6c25..dcfaf428e 100644
--- a/conf/inspircd.conf.example
+++ b/conf/inspircd.conf.example
@@ -522,10 +522,6 @@
# banned from the server.
moronbanner="You're banned! Email haha@abuse.com with the ERROR line below for help."
- # exemptchanops: Defines what channel modes channel operators are
- # exempt from. Supported modes are +TCGfcSFBgN. Defaults to off.
- exemptchanops=""
-
# invitebypassmodes: This allows /invite to bypass other channel modes.
# (Such as +k, +j, +l, etc)
invitebypassmodes="yes">
diff --git a/conf/modules.conf.example b/conf/modules.conf.example
index 9bc9dca05..691068a73 100644
--- a/conf/modules.conf.example
+++ b/conf/modules.conf.example
@@ -697,6 +697,21 @@
# http://wiki.inspircd.org/Modules/dnsbl #
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# Exempt Channel Operators Module: Provides support for allowing #
+# channel operators to be exempt from some channel modes. Supported #
+# modes are blockcaps, noctcp, blockcolor, nickflood, flood, censor, #
+# filter, regmoderated, nonick, nonotice, and stripcolor. #
+#<module name="m_exemptchanops.so"> #
+# #
+#-#-#-#-#-#-#-#-#-#- EXEMPTCHANOPS CONFIGURATION -#-#-#-#-#-#-#-#-#-#
+# alwaysexempt - Modes channel operators are always exempt from,
+# regardless of channel setting.
+# neverexempt - Modes channel operators are never exempt from,
+# regardless of channel setting.
+#<exemptchanops alwaysexempt="nickflood censor flood filter" neverexempt="regmoderated">
+
+
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Filter module: Provides message filtering, similar to SPAMFILTER.
#<module name="m_filter.so">
# #
diff --git a/include/configreader.h b/include/configreader.h
index 893259b7c..bc5a09f02 100644
--- a/include/configreader.h
+++ b/include/configreader.h
@@ -14,13 +14,6 @@
#ifndef INSPIRCD_CONFIGREADER
#define INSPIRCD_CONFIGREADER
-/* handy defines */
-
-/** Determines if a channel op is exempt from given mode m,
- * in config of server instance s.
- */
-#define CHANOPS_EXEMPT(m) (ServerInstance->Config->ExemptChanOps[(unsigned char)m])
-
#include <sstream>
#include <string>
#include <vector>
@@ -385,11 +378,6 @@ class CoreExport ServerConfig : public classbase
*/
bool HideModeLists[256];
- /** If this is set to true, then channel operators
- * are exempt from this channel mode. Used for +Sc etc.
- */
- bool ExemptChanOps[256];
-
/** The number of seconds the DNS subsystem
* will wait before timing out any request.
*/
diff --git a/include/modules.h b/include/modules.h
index 972dabb43..0a1c759e5 100644
--- a/include/modules.h
+++ b/include/modules.h
@@ -341,7 +341,7 @@ enum Implementation
I_OnPostOper, I_OnSyncNetwork, I_OnSetAway, I_OnUserList, I_OnPostCommand, I_OnPostJoin,
I_OnWhoisLine, I_OnBuildNeighborList, I_OnGarbageCollect,
I_OnText, I_OnPassCompare, I_OnRunTestSuite, I_OnNamesListItem, I_OnNumeric, I_OnHookIO,
- I_OnPreRehash, I_OnModuleRehash, I_OnSendWhoLine, I_OnChangeIdent,
+ I_OnPreRehash, I_OnModuleRehash, I_OnSendWhoLine, I_OnChangeIdent, I_OnChannelRestrictionApply,
I_END
};
@@ -1279,6 +1279,12 @@ class CoreExport Module : public Extensible
* @param line The raw line to send; modifiable, if empty no line will be returned.
*/
virtual void OnSendWhoLine(User* source, User* user, Channel* channel, std::string& line);
+
+ /** Called to check whether a channel restriction mode applies to a user on it
+ * @return MOD_RES_DENY to apply the restriction, MOD_RES_ALLOW to bypass
+ * the restriction, or MOD_RES_PASSTHRU to check restriction status normally
+ */
+ virtual ModResult OnChannelRestrictionApply(Membership* memb, Channel* chan, const char* restriction);
};
diff --git a/src/configreader.cpp b/src/configreader.cpp
index ac0dc2596..7248ff099 100644
--- a/src/configreader.cpp
+++ b/src/configreader.cpp
@@ -382,14 +382,6 @@ static bool ValidateModeLists(ServerConfig* conf, const char*, const char*, Valu
return true;
}
-static bool ValidateExemptChanOps(ServerConfig* conf, const char*, const char*, ValueItem &data)
-{
- memset(conf->ExemptChanOps, 0, sizeof(conf->ExemptChanOps));
- for (const unsigned char* x = (const unsigned char*)data.GetString(); *x; ++x)
- conf->ExemptChanOps[*x] = true;
- return true;
-}
-
static bool ValidateInvite(ServerConfig* conf, const char*, const char*, ValueItem &data)
{
const std::string& v = data.GetValue();
@@ -801,7 +793,6 @@ static const InitialConfig Values[] = {
{"security", "announceinvites", "1", NULL, DT_NOTHING, ValidateInvite},
{"options", "hostintopic", "1", new ValueContainerBool (&ServerConfig::FullHostInTopic), DT_BOOLEAN, NULL},
{"security", "hidemodes", "", NULL, DT_NOTHING, ValidateModeLists},
- {"options", "exemptchanops","", NULL, DT_NOTHING, ValidateExemptChanOps},
{"security", "maxtargets", "20", new ValueContainerUInt (&ServerConfig::MaxTargets), DT_INTEGER, ValidateMaxTargets},
{"options", "defaultmodes", "nt", new ValueContainerString (&ServerConfig::DefaultModes), DT_CHARPTR, NULL},
{"pid", "file", "", new ValueContainerString (&ServerConfig::PID), DT_CHARPTR, NULL},
diff --git a/src/modules.cpp b/src/modules.cpp
index 921e710f9..3e9fa84de 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -150,6 +150,7 @@ void Module::OnNamesListItem(User*, Membership*, std::string&, std::string&) {
ModResult Module::OnNumeric(User*, unsigned int, const std::string&) { return MOD_RES_PASSTHRU; }
void Module::OnHookIO(StreamSocket*, ListenSocketBase*) { }
void Module::OnSendWhoLine(User*, User*, Channel*, std::string&) { }
+ModResult Module::OnChannelRestrictionApply(Membership*, Channel*, const char*) { return MOD_RES_PASSTHRU; }
ModuleManager::ModuleManager() : ModCount(0)
{
diff --git a/src/modules/m_blockcaps.cpp b/src/modules/m_blockcaps.cpp
index 7a696035a..e21e1da88 100644
--- a/src/modules/m_blockcaps.cpp
+++ b/src/modules/m_blockcaps.cpp
@@ -16,7 +16,7 @@
/* $ModDesc: Provides support to block all-CAPS channel messages and notices */
-/** Handles the +P channel mode
+/** Handles the +B channel mode
*/
class BlockCaps : public SimpleChannelModeHandler
{
@@ -59,11 +59,11 @@ public:
return MOD_RES_PASSTHRU;
Channel* c = (Channel*)dest;
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"blockcaps"));
- if (CHANOPS_EXEMPT('B') && c->GetPrefixValue(user) == OP_VALUE)
- {
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
if (!c->GetExtBanStatus(user, 'B').check(!c->IsModeSet('B')))
{
diff --git a/src/modules/m_blockcolor.cpp b/src/modules/m_blockcolor.cpp
index eba648126..9075bd8c7 100644
--- a/src/modules/m_blockcolor.cpp
+++ b/src/modules/m_blockcolor.cpp
@@ -47,11 +47,11 @@ class ModuleBlockColour : public Module
if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user)))
{
Channel* c = (Channel*)dest;
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"blockcolor"));
- if (CHANOPS_EXEMPT('c') && c->GetPrefixValue(user) == OP_VALUE)
- {
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
if (!c->GetExtBanStatus(user, 'c').check(!c->IsModeSet('c')))
{
diff --git a/src/modules/m_censor.cpp b/src/modules/m_censor.cpp
index fc640624c..4b08e3711 100644
--- a/src/modules/m_censor.cpp
+++ b/src/modules/m_censor.cpp
@@ -75,10 +75,11 @@ class ModuleCensor : public Module
{
active = ((Channel*)dest)->IsModeSet('G');
Channel* c = (Channel*)dest;
- if (CHANOPS_EXEMPT('G') && c->GetPrefixValue(user) == OP_VALUE)
- {
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"censor"));
+
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
}
if (!active)
diff --git a/src/modules/m_chanfilter.cpp b/src/modules/m_chanfilter.cpp
index d87de1207..cb5f22855 100644
--- a/src/modules/m_chanfilter.cpp
+++ b/src/modules/m_chanfilter.cpp
@@ -85,7 +85,10 @@ class ModuleChanFilter : public Module
virtual ModResult ProcessMessages(User* user,Channel* chan,std::string &text)
{
- if (!IS_LOCAL(user) || (CHANOPS_EXEMPT('g') && chan->GetPrefixValue(user) == OP_VALUE))
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (chan->GetUser(user),chan,"filter"));
+
+ if (!IS_LOCAL(user) || res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
modelist* list = cf.extItem.get(chan);
diff --git a/src/modules/m_exemptchanops.cpp b/src/modules/m_exemptchanops.cpp
new file mode 100644
index 000000000..ae2a80b02
--- /dev/null
+++ b/src/modules/m_exemptchanops.cpp
@@ -0,0 +1,130 @@
+/* +------------------------------------+
+ * | Inspire Internet Relay Chat Daemon |
+ * +------------------------------------+
+ *
+ * InspIRCd: (C) 2002-2009 InspIRCd Development Team
+ * See: http://wiki.inspircd.org/Credits
+ *
+ * This program is free but copyrighted software; see
+ * the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE
+#define _SCL_SECURE_NO_DEPRECATE
+
+#include "inspircd.h"
+#include "u_listmode.h"
+
+/* $ModDesc: Provides the ability to allow channel operators to be exempt from certain modes. */
+/* $ModDep: ../../include/u_listmode.h */
+
+/** Handles channel mode +X
+ */
+class ExemptChanOps : public ListModeBase
+{
+ public:
+ ExemptChanOps(Module* Creator) : ListModeBase(Creator, "exemptchanops", 'X', "End of channel exemptchanops list", 954, 953, false, "exemptchanops") { }
+
+ virtual bool ValidateParam(User* user, Channel* chan, std::string &word)
+ {
+ if ((word.length() > 35) || (word.empty()))
+ {
+ user->WriteNumeric(955, "%s %s %s :word is too %s for exemptchanops list",user->nick.c_str(), chan->name.c_str(), word.c_str(), (word.empty() ? "short" : "long"));
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual bool TellListTooLong(User* user, Channel* chan, std::string &word)
+ {
+ user->WriteNumeric(959, "%s %s %s :Channel exemptchanops list is full", user->nick.c_str(), chan->name.c_str(), word.c_str());
+ return true;
+ }
+
+ virtual void TellAlreadyOnList(User* user, Channel* chan, std::string &word)
+ {
+ user->WriteNumeric(957, "%s %s :The word %s is already on the exemptchanops list",user->nick.c_str(), chan->name.c_str(), word.c_str());
+ }
+
+ virtual void TellNotSet(User* user, Channel* chan, std::string &word)
+ {
+ user->WriteNumeric(958, "%s %s :No such exemptchanops word is set",user->nick.c_str(), chan->name.c_str());
+ }
+};
+
+class ModuleExemptChanOps : public Module
+{
+ ExemptChanOps ec;
+ std::string alwaysexempt, neverexempt;
+
+ public:
+
+ ModuleExemptChanOps()
+ : ec(this)
+ {
+ if (!ServerInstance->Modes->AddMode(&ec))
+ throw ModuleException("Could not add new modes!");
+
+ ec.DoImplements(this);
+ Implementation eventlist[] = { I_OnChannelDelete, I_OnChannelRestrictionApply, I_OnRehash, I_OnSyncChannel };
+ ServerInstance->Modules->Attach(eventlist, this, 4);
+
+ OnRehash(NULL);
+ ServerInstance->Modules->PublishInterface("ChannelBanList", this);
+ }
+
+ virtual Version GetVersion()
+ {
+ return Version("Provides the ability to allow channel operators to be exempt from certain modes.",VF_VENDOR|VF_COMMON,API_VERSION);
+ }
+
+ virtual void OnRehash(User* user)
+ {
+ ConfigReader Conf;
+ alwaysexempt = Conf.ReadValue("exemptchanops", "alwaysexempt", 0);
+ neverexempt = Conf.ReadValue("exemptchanops", "neverexempt", 0);
+ ec.DoRehash();
+ }
+
+ virtual void OnCleanup(int target_type, void* item)
+ {
+ ec.DoCleanup(target_type, item);
+ }
+
+ virtual void OnSyncChannel(Channel* chan, Module* proto, void* opaque)
+ {
+ ec.DoSyncChannel(chan, proto, opaque);
+ }
+
+ virtual ModResult OnChannelRestrictionApply(Membership* memb, Channel* chan, const char* restriction)
+ {
+ irc::spacesepstream allowstream(alwaysexempt), denystream(neverexempt);
+ std::string current;
+
+ if (memb->getRank() != OP_VALUE)
+ return MOD_RES_PASSTHRU; // They're not opped, so we don't exempt them
+ while(denystream.GetToken(current))
+ if (!strcasecmp(restriction, current.c_str())) return MOD_RES_PASSTHRU; // This mode is set to never allow exemptions in the config
+ while(allowstream.GetToken(current))
+ if (!strcasecmp(restriction, current.c_str())) return MOD_RES_ALLOW; // This mode is set to always allow exemptions in the config
+
+ modelist* list = ec.extItem.get(chan);
+
+ if (!list) return MOD_RES_PASSTHRU;
+ for (modelist::iterator i = list->begin(); i != list->end(); ++i)
+ if (!strcasecmp(restriction, i->mask.c_str()))
+ return MOD_RES_ALLOW; // They're opped, and the channel lets ops bypass this mode. Allow regardless of restrictions
+
+ return MOD_RES_PASSTHRU;
+ }
+
+ virtual ~ModuleExemptChanOps()
+ {
+ ServerInstance->Modules->UnpublishInterface("ChannelBanList", this);
+ }
+};
+
+MODULE_INIT(ModuleExemptChanOps)
diff --git a/src/modules/m_messageflood.cpp b/src/modules/m_messageflood.cpp
index 542aedd41..8a521ebab 100644
--- a/src/modules/m_messageflood.cpp
+++ b/src/modules/m_messageflood.cpp
@@ -207,10 +207,10 @@ class ModuleMsgFlood : public Module
ModResult ProcessMessages(User* user,Channel* dest, const std::string &text)
{
- if (!IS_LOCAL(user) || (CHANOPS_EXEMPT('f') && dest->GetPrefixValue(user) == OP_VALUE))
- {
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (dest->GetUser(user),dest,"flood"));
+ if (!IS_LOCAL(user) || res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
floodsettings *f = mf.ext.get(dest);
if (f)
diff --git a/src/modules/m_nickflood.cpp b/src/modules/m_nickflood.cpp
index 748ecf1d5..26d04835a 100644
--- a/src/modules/m_nickflood.cpp
+++ b/src/modules/m_nickflood.cpp
@@ -213,11 +213,13 @@ class ModuleNickFlood : public Module
for (UCListIter i = user->chans.begin(); i != user->chans.end(); i++)
{
Channel *channel = *i;
+ ModResult res;
nickfloodsettings *f = nf.ext.get(channel);
if (f)
{
- if (CHANOPS_EXEMPT('F') && channel->GetPrefixValue(user) == OP_VALUE)
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (channel->GetUser(user),channel,"nickflood"));
+ if (res == MOD_RES_ALLOW)
continue;
if (f->islocked())
@@ -250,11 +252,13 @@ class ModuleNickFlood : public Module
for (UCListIter i = user->chans.begin(); i != user->chans.end(); ++i)
{
Channel *channel = *i;
+ ModResult res;
nickfloodsettings *f = nf.ext.get(channel);
if (f)
{
- if (CHANOPS_EXEMPT('F') && channel->GetPrefixValue(user) == OP_VALUE)
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (channel->GetUser(user),channel,"nickflood"));
+ if (res == MOD_RES_ALLOW)
return;
/* moved this here to avoid incrementing the counter for nick
diff --git a/src/modules/m_noctcp.cpp b/src/modules/m_noctcp.cpp
index 5dfbf76d2..1d360abe1 100644
--- a/src/modules/m_noctcp.cpp
+++ b/src/modules/m_noctcp.cpp
@@ -78,11 +78,11 @@ class ModuleNoCTCP : public Module
if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user)))
{
Channel* c = (Channel*)dest;
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"noctcp"));
- if (CHANOPS_EXEMPT('C') && c->GetPrefixValue(user) == OP_VALUE)
- {
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
if (!c->GetExtBanStatus(user, 'C').check(!c->IsModeSet('C')))
{
diff --git a/src/modules/m_nonicks.cpp b/src/modules/m_nonicks.cpp
index 22b0067cc..3e378d59f 100644
--- a/src/modules/m_nonicks.cpp
+++ b/src/modules/m_nonicks.cpp
@@ -85,7 +85,10 @@ class ModuleNoNickChange : public Module
{
Channel* curr = *i;
- if (CHANOPS_EXEMPT('N') && curr->GetPrefixValue(user) == OP_VALUE)
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (curr->GetUser(user),curr,"nonick"));
+
+ if (res == MOD_RES_ALLOW)
continue;
if (!curr->GetExtBanStatus(user, 'N').check(!curr->IsModeSet('N')))
diff --git a/src/modules/m_nonotice.cpp b/src/modules/m_nonotice.cpp
index 169626e61..188f08908 100644
--- a/src/modules/m_nonotice.cpp
+++ b/src/modules/m_nonotice.cpp
@@ -42,6 +42,7 @@ class ModuleNoNotice : public Module
virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
{
+ ModResult res;
if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user)))
{
Channel* c = (Channel*)dest;
@@ -52,11 +53,9 @@ class ModuleNoNotice : public Module
// ulines are exempt.
return MOD_RES_PASSTHRU;
}
- else if (CHANOPS_EXEMPT('T') && c->GetPrefixValue(user) == OP_VALUE)
- {
- // channel ops are exempt if set in conf.
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"nonotice"));
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
else
{
user->WriteNumeric(ERR_CANNOTSENDTOCHAN, "%s %s :Can't send NOTICE to channel (+T set)",user->nick.c_str(), c->name.c_str());
diff --git a/src/modules/m_services_account.cpp b/src/modules/m_services_account.cpp
index 45baaf90e..7f4e6d43f 100644
--- a/src/modules/m_services_account.cpp
+++ b/src/modules/m_services_account.cpp
@@ -173,8 +173,10 @@ class ModuleServicesAccount : public Module
if (target_type == TYPE_CHANNEL)
{
Channel* c = (Channel*)dest;
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (c->GetUser(user),c,"regmoderated"));
- if (c->IsModeSet('M') && !is_registered)
+ if (c->IsModeSet('M') && !is_registered && res != MOD_RES_ALLOW)
{
// user messaging a +M channel and is not registered
user->WriteNumeric(477, ""+std::string(user->nick)+" "+std::string(c->name)+" :You need to be identified to a registered account to message this channel");
diff --git a/src/modules/m_stripcolor.cpp b/src/modules/m_stripcolor.cpp
index e610987d3..2c2b2363f 100644
--- a/src/modules/m_stripcolor.cpp
+++ b/src/modules/m_stripcolor.cpp
@@ -110,13 +110,11 @@ class ModuleStripColor : public Module
else if (target_type == TYPE_CHANNEL)
{
Channel* t = (Channel*)dest;
+ ModResult res;
+ FIRST_MOD_RESULT(OnChannelRestrictionApply, res, (t->GetUser(user),t,"stripcolor"));
- // check if we allow ops to bypass filtering, if we do, check if they're opped accordingly.
- // note: short circut logic here, don't wreck it. -- w00t
- if (CHANOPS_EXEMPT('S') && t->GetPrefixValue(user) == OP_VALUE)
- {
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- }
active = !t->GetExtBanStatus(user, 'S').check(!t->IsModeSet('S'));
}