aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/configreader.h9
-rw-r--r--include/modules.h31
-rw-r--r--include/modules/isupport.h2
-rw-r--r--include/users.h34
-rw-r--r--src/configreader.cpp7
-rw-r--r--src/coremods/core_channel/core_channel.cpp2
-rw-r--r--src/coremods/core_info/core_info.h4
-rw-r--r--src/coremods/core_oper/core_oper.cpp15
-rw-r--r--src/coremods/core_user/core_user.cpp94
-rw-r--r--src/modules.cpp4
-rw-r--r--src/modules/m_account.cpp10
-rw-r--r--src/modules/m_dnsbl.cpp14
-rw-r--r--src/modules/m_gateway.cpp12
-rw-r--r--src/modules/m_geoclass.cpp8
-rw-r--r--src/modules/m_ident.cpp8
-rw-r--r--src/modules/m_sslinfo.cpp10
-rw-r--r--src/usermanager.cpp10
-rw-r--r--src/users.cpp247
18 files changed, 263 insertions, 258 deletions
diff --git a/include/configreader.h b/include/configreader.h
index fe7e4bc5d..22192ea8f 100644
--- a/include/configreader.h
+++ b/include/configreader.h
@@ -298,7 +298,7 @@ public:
/** Holds a complete list of all connect blocks
*/
- typedef std::vector<ConnectClass::Ptr> ClassVector;
+ typedef std::vector<std::shared_ptr<ConnectClass>> ClassVector;
/** Holds the oper accounts from the server config. */
typedef insp::flat_map<std::string, std::shared_ptr<OperAccount>> OperAccountMap;
@@ -404,13 +404,6 @@ public:
*/
int MaxConn;
- /** If we should check for clones during CheckClass() in AddUser()
- * Setting this to false allows to not trigger on maxclones for users
- * that may belong to another class after DNS-lookup is complete.
- * It does, however, make the server spend more time on users we may potentially not want.
- */
- bool CCOnConnect;
-
/** The soft limit value assigned to the irc server.
* The IRC server will not allow more than this
* number of local users.
diff --git a/include/modules.h b/include/modules.h
index f13d6fdbf..b2c6e355b 100644
--- a/include/modules.h
+++ b/include/modules.h
@@ -139,6 +139,7 @@ enum Implementation
I_OnAddLine,
I_OnBackgroundTimer,
I_OnBuildNeighborList,
+ I_OnChangeConnectClass,
I_OnChangeHost,
I_OnChangeIdent,
I_OnChangeRealHost,
@@ -165,6 +166,7 @@ enum Implementation
I_OnOperLogin,
I_OnOperLogout,
I_OnPassCompare,
+ I_OnPostChangeConnectClass,
I_OnPostChangeRealHost,
I_OnPostCommand,
I_OnPostConnect,
@@ -172,6 +174,7 @@ enum Implementation
I_OnPostOperLogin,
I_OnPostOperLogout,
I_OnPostTopicChange,
+ I_OnPreChangeConnectClass,
I_OnPreChangeHost,
I_OnPreChangeRealName,
I_OnPreCommand,
@@ -183,7 +186,6 @@ enum Implementation
I_OnSendSnotice,
I_OnServiceAdd,
I_OnServiceDel,
- I_OnSetConnectClass,
I_OnShutdown,
I_OnUnloadModule,
I_OnUserConnect,
@@ -885,12 +887,6 @@ public:
*/
virtual void OnGarbageCollect();
- /** Called when a user's connect class is being matched
- * @return MOD_RES_ALLOW to force the class to match, MOD_RES_DENY to forbid it, or
- * MOD_RES_PASSTHRU to allow normal matching (by host/port).
- */
- virtual ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass);
-
virtual ModResult OnNumeric(User* user, const Numeric::Numeric& numeric);
/** Called whenever a local user's remote address is set or changed.
@@ -953,6 +949,27 @@ public:
* @param oper The server operator account they were logged out of.
*/
virtual void OnPostOperLogout(User* user, const std::shared_ptr<OperAccount>& oper);
+
+ /** Called when trying to find a connect class for a user.
+ * @param user The user that needs a new connect class.
+ * @param klass The connect class to check the suitability of.
+ * @return MOD_RES_ALLOW to select this connect class, MOD_RES_DENY to reject this connect
+ * class, or MOD_RES_PASSTHRU to let another module handle the event.
+ */
+ virtual ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass);
+
+ /** Called when a user is about to be assigned to a connect class.
+ * @param user The user that is being assigned to a connect class.
+ * @param klass The connect class the user is being assigned to.
+ * @param force Whether the connect class was explicitly picked (e.g. via <oper:class>).
+ */
+ virtual void OnChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass, bool force);
+
+ /** Called when a user has bee assigned to a connect class.
+ * @param user The user that was assigned to a connect class.
+ * @param force Whether the connect class was explicitly picked (e.g. via <oper:class>).
+ */
+ virtual void OnPostChangeConnectClass(LocalUser* user, bool force);
};
/** ModuleManager takes care of all things module-related
diff --git a/include/modules/isupport.h b/include/modules/isupport.h
index ef7916d74..264760299 100644
--- a/include/modules/isupport.h
+++ b/include/modules/isupport.h
@@ -45,7 +45,7 @@ protected:
public:
virtual void OnBuildISupport(TokenMap& tokens) { }
- virtual void OnBuildClassISupport(const ConnectClass::Ptr& klass, TokenMap& tokens) { }
+ virtual void OnBuildClassISupport(const std::shared_ptr<ConnectClass>& klass, TokenMap& tokens) { }
};
class ISupport::EventProvider final
diff --git a/include/users.h b/include/users.h
index 0bc6ed534..20af4436d 100644
--- a/include/users.h
+++ b/include/users.h
@@ -42,9 +42,6 @@
class CoreExport ConnectClass final
{
public:
- /** A shared pointer to a connect class. */
- typedef std::shared_ptr<ConnectClass> Ptr;
-
/** An enumeration of possible types of connect class. */
enum Type
: uint8_t
@@ -125,17 +122,20 @@ public:
/** The maximum number of bytes that users in this class can have in their send queue before their commands stop being processed. */
unsigned long softsendqmax = 4096UL;
+ /** The number of users who are currently assigned to this class. */
+ unsigned long use_count = 0UL;
+
/** Creates a new connect class from a config tag. */
ConnectClass(std::shared_ptr<ConfigTag> tag, Type type, const std::vector<std::string>& masks);
/** Creates a new connect class with a parent from a config tag. */
- ConnectClass(std::shared_ptr<ConfigTag> tag, Type type, const std::vector<std::string>& masks, const ConnectClass::Ptr& parent);
+ ConnectClass(std::shared_ptr<ConfigTag> tag, Type type, const std::vector<std::string>& masks, const std::shared_ptr<ConnectClass>& parent);
/** Configures this connect class using the config from the specified tag. */
void Configure(const std::string& classname, std::shared_ptr<ConfigTag> tag);
/** Update the settings in this block to match the given class */
- void Update(const ConnectClass::Ptr& klass);
+ void Update(const std::shared_ptr<ConnectClass>& klass);
/** Retrieves the name of this connect class. */
const std::string& GetName() const { return name; }
@@ -754,7 +754,7 @@ class CoreExport LocalUser final
{
private:
/** The connect class this user is in. */
- ConnectClass::Ptr connectclass;
+ std::shared_ptr<ConnectClass> connectclass;
/** Message list, can be passed to the two parameter Send(). */
static ClientProtocol::MessageList sendmsglist;
@@ -807,11 +807,7 @@ public:
/** Get the connect class which this user belongs to.
* @return A pointer to this user's connect class.
*/
- const ConnectClass::Ptr& GetClass() const { return connectclass; }
-
- /** Call this method to find the matching \<connect> for a user, and to check them against it.
- */
- void CheckClass(bool clone_count = true);
+ const std::shared_ptr<ConnectClass>& GetClass() const { return connectclass; }
/** Server address and port that this user is connected to.
*/
@@ -856,14 +852,20 @@ public:
*/
void FullConnect();
- /** Set the connect class to which this user belongs to.
- * @param explicit_name Set this string to tie the user to a specific class name. Otherwise, the class is fitted by checking \<connect> tags from the configuration file.
- */
- void SetClass(const std::string& explicit_name = "");
-
/** @copydoc User::ChangeRemoteAddress */
void ChangeRemoteAddress(const irc::sockets::sockaddrs& sa) override;
+ /** Change the connect class for this user.
+ * @param klass The connect class the user should be assigned to.
+ * @param force Whether the connect class was explicitly picked (e.g. via <oper:class>).
+ */
+ void ChangeConnectClass(const std::shared_ptr<ConnectClass>& klass, bool force);
+
+ /** Find a new connect class for this user.
+ * @return True if an allow-type connect class was found for the user. Otherwise, false.
+ */
+ bool FindConnectClass();
+
/** Send a NOTICE message from the local server to the user.
* The message will be sent even if the user is connected to a remote server.
* @param text Text to send
diff --git a/src/configreader.cpp b/src/configreader.cpp
index 539901764..65df85bfd 100644
--- a/src/configreader.cpp
+++ b/src/configreader.cpp
@@ -152,7 +152,7 @@ void ServerConfig::CrossCheckOperBlocks()
void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current)
{
- typedef std::map<std::pair<std::string, ConnectClass::Type>, ConnectClass::Ptr> ClassMap;
+ typedef std::map<std::pair<std::string, ConnectClass::Type>, std::shared_ptr<ConnectClass>> ClassMap;
ClassMap oldBlocksByMask;
if (current)
{
@@ -198,7 +198,7 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current)
continue;
}
- ConnectClass::Ptr parent;
+ std::shared_ptr<ConnectClass> parent;
std::string parentName = tag->getString("parent");
if (!parentName.empty())
{
@@ -250,7 +250,7 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current)
ClassMap::iterator oldMask = oldBlocksByMask.find(std::make_pair(mask, me->type));
if (oldMask != oldBlocksByMask.end())
{
- ConnectClass::Ptr old = oldMask->second;
+ std::shared_ptr<ConnectClass> old = oldMask->second;
oldBlocksByMask.erase(oldMask);
old->Update(me);
me = old;
@@ -300,7 +300,6 @@ void ServerConfig::Fill()
throw CoreException("You must restart to change the server id");
}
SoftLimit = ConfValue("performance")->getUInt("softlimit", (SocketEngine::GetMaxFds() > 0 ? SocketEngine::GetMaxFds() : LONG_MAX), 10);
- CCOnConnect = ConfValue("performance")->getBool("clonesonconnect", true);
MaxConn = static_cast<int>(ConfValue("performance")->getUInt("somaxconn", SOMAXCONN));
TimeSkipWarn = ConfValue("performance")->getDuration("timeskipwarn", 2, 0, 30);
XLineMessage = options->getString("xlinemessage", "You're banned!", 1);
diff --git a/src/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp
index 2046338e0..82aa6a22a 100644
--- a/src/coremods/core_channel/core_channel.cpp
+++ b/src/coremods/core_channel/core_channel.cpp
@@ -237,7 +237,7 @@ public:
tokens["MAXLIST"] = stdalgo::string::join(limits, ',');
}
- void OnBuildClassISupport(const ConnectClass::Ptr& klass, ISupport::TokenMap& tokens) override
+ void OnBuildClassISupport(const std::shared_ptr<ConnectClass>& klass, ISupport::TokenMap& tokens) override
{
tokens["CHANLIMIT"] = InspIRCd::Format("#:%lu", klass->maxchans);
}
diff --git a/src/coremods/core_info/core_info.h b/src/coremods/core_info/core_info.h
index 29cf7c8f5..1025b7fc6 100644
--- a/src/coremods/core_info/core_info.h
+++ b/src/coremods/core_info/core_info.h
@@ -28,11 +28,11 @@ class ISupportManager final
{
private:
/** The generated numerics which are sent to clients. */
- typedef insp::flat_map<ConnectClass::Ptr, std::vector<Numeric::Numeric>> NumericMap;
+ typedef insp::flat_map<std::shared_ptr<ConnectClass>, std::vector<Numeric::Numeric>> NumericMap;
NumericMap cachednumerics;
/** The tokens which were generated by the last update. */
- typedef insp::flat_map<ConnectClass::Ptr, ISupport::TokenMap> TokenMap;
+ typedef insp::flat_map<std::shared_ptr<ConnectClass>, ISupport::TokenMap> TokenMap;
TokenMap cachedtokens;
/** Provider for the ISupport::EventListener event. */
diff --git a/src/coremods/core_oper/core_oper.cpp b/src/coremods/core_oper/core_oper.cpp
index c08a0b5f3..0167f05be 100644
--- a/src/coremods/core_oper/core_oper.cpp
+++ b/src/coremods/core_oper/core_oper.cpp
@@ -112,9 +112,18 @@ public:
if (!vhost.empty())
user->ChangeDisplayedHost(vhost);
- const std::string klass = luser->oper->GetConfig()->getString("class");
- if (!klass.empty())
- luser->SetClass(klass);
+ const std::string klassname = luser->oper->GetConfig()->getString("class");
+ if (!klassname.empty())
+ {
+ for (const auto& klass : ServerInstance->Config->Classes)
+ {
+ if (klassname == klass->GetName())
+ {
+ luser->ChangeConnectClass(klass, true);
+ break;
+ }
+ }
+ }
}
ModResult OnStats(Stats::Context& stats) override
diff --git a/src/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp
index 7277d54e3..7075c50f3 100644
--- a/src/coremods/core_user/core_user.cpp
+++ b/src/coremods/core_user/core_user.cpp
@@ -137,6 +137,7 @@ void MessageWrapper::ReadConfig(const char* prefixname, const char* suffixname,
class CoreModUser final
: public Module
{
+private:
CommandAway cmdaway;
CommandNick cmdnick;
CommandPart cmdpart;
@@ -148,6 +149,7 @@ class CoreModUser final
CommandIson cmdison;
CommandUserhost cmduserhost;
SimpleUserMode invisiblemode;
+ bool clonesonconnect;
public:
CoreModUser()
@@ -166,10 +168,102 @@ public:
{
}
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
+ {
+ bool conndone = user->connected != User::CONN_NONE;
+ if (klass->config->getBool("connected", klass->config->getBool("registered", conndone)) != conndone)
+ {
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires that the user is %s.",
+ klass->GetName().c_str(), conndone ? "not fully connected" : "fully connected");
+ return MOD_RES_DENY;
+ }
+
+ bool hostmatches = false;
+ for (const auto& host : klass->GetHosts())
+ {
+ if (InspIRCd::MatchCIDR(user->GetIPString(), host) || InspIRCd::MatchCIDR(user->GetRealHost(), host))
+ {
+ hostmatches = true;
+ break;
+ }
+ }
+ if (!hostmatches)
+ {
+ const std::string hosts = stdalgo::string::join(klass->GetHosts());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as neither the host (%s) nor the IP (%s) matches %s.",
+ klass->GetName().c_str(), user->GetRealHost().c_str(), user->GetIPString().c_str(), hosts.c_str());
+ return MOD_RES_DENY;
+ }
+
+ if (klass->limit && klass->use_count >= klass->limit)
+ {
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it has reached its user limit (%lu/%lu).",
+ klass->GetName().c_str(), klass->use_count, klass->limit);
+ return MOD_RES_DENY;
+ }
+
+ if (conndone && !klass->password.empty() && !ServerInstance->PassCompare(klass->password, user->password, klass->passwordhash))
+ {
+ const char* error = user->password.empty() ? "one was not provided" : "the provided password was incorrect";
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as requires a password and %s.",
+ klass->GetName().c_str(), error);
+ return MOD_RES_DENY;
+ }
+
+ if (!klass->ports.empty() && !klass->ports.count(user->server_sa.port()))
+ {
+ const std::string portstr = stdalgo::string::join(klass->ports);
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the connection port (%hu) is not any of %s.",
+ klass->GetName().c_str(), user->server_sa.port(), portstr.c_str());
+ return MOD_RES_DENY;
+ }
+
+ return MOD_RES_PASSTHRU;
+ }
+
+ void OnChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass, bool force) override
+ {
+ if (klass->type == ConnectClass::DENY)
+ {
+ ServerInstance->Users.QuitUser(user, klass->config->getString("reason", "You are not allowed to connect to this server", 1));
+ return;
+ }
+
+ // If a user wasn't forced into a class (e.g. via <oper:class>) then we need to check limits.
+ if (!force && (clonesonconnect || user->connected != User::CONN_NONE))
+ {
+ const UserManager::CloneCounts& clonecounts = ServerInstance->Users.GetCloneCounts(user);
+ if (klass->maxlocal && clonecounts.local > klass->maxlocal)
+ {
+ ServerInstance->Users.QuitUser(user, "No more local connections allowed from your host via this connect class.");
+ if (klass->maxconnwarn)
+ {
+ ServerInstance->SNO.WriteToSnoMask('a', "WARNING: maximum local connections for the %s class (%ld) exceeded by %s",
+ klass->GetName().c_str(), klass->maxlocal, user->GetIPString().c_str());
+ }
+ return;
+ }
+
+ if (klass->maxglobal && clonecounts.global > klass->maxglobal)
+ {
+ ServerInstance->Users.QuitUser(user, "No more global connections allowed from your host via this connect class.");
+ if (klass->maxconnwarn)
+ {
+ ServerInstance->SNO.WriteToSnoMask('a', "WARNING: maximum global connections for the %s class (%ld) exceeded by %s",
+ klass->name.c_str(), klass->maxglobal, user->GetIPString().c_str());
+ }
+ return;
+ }
+ }
+ }
+
void ReadConfig(ConfigStatus& status) override
{
cmdpart.msgwrap.ReadConfig("prefixpart", "suffixpart", "fixedpart");
cmdquit.msgwrap.ReadConfig("prefixquit", "suffixquit", "fixedquit");
+
+ auto performance = ServerInstance->Config->ConfValue("performance");
+ clonesonconnect = performance->getBool("clonesonconnect", true);
}
};
diff --git a/src/modules.cpp b/src/modules.cpp
index cce4f91f1..39ae6f2e3 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -152,7 +152,6 @@ ModResult Module::OnChannelPreDelete(Channel*) { DetachEvent(I_OnChannelPreDelet
void Module::OnChannelDelete(Channel*) { DetachEvent(I_OnChannelDelete); }
void Module::OnBuildNeighborList(User*, User::NeighborList&, User::NeighborExceptions&) { DetachEvent(I_OnBuildNeighborList); }
void Module::OnGarbageCollect() { DetachEvent(I_OnGarbageCollect); }
-ModResult Module::OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) { DetachEvent(I_OnSetConnectClass); return MOD_RES_PASSTHRU; }
void Module::OnUserMessage(User*, const MessageTarget&, const MessageDetails&) { DetachEvent(I_OnUserMessage); }
ModResult Module::OnNumeric(User*, const Numeric::Numeric&) { DetachEvent(I_OnNumeric); return MOD_RES_PASSTHRU; }
ModResult Module::OnAcceptConnection(int, ListenSocket*, const irc::sockets::sockaddrs&, const irc::sockets::sockaddrs&) { DetachEvent(I_OnAcceptConnection); return MOD_RES_PASSTHRU; }
@@ -166,6 +165,9 @@ void Module::OnOperLogin(User*, const std::shared_ptr<OperAccount>&, bool) { De
void Module::OnPostOperLogin(User*, bool) { DetachEvent(I_OnPostOperLogin); }
void Module::OnOperLogout(User*) { DetachEvent(I_OnOperLogout); }
void Module::OnPostOperLogout(User*, const std::shared_ptr<OperAccount>&) { DetachEvent(I_OnPostOperLogout); }
+ModResult Module::OnPreChangeConnectClass(LocalUser*, const std::shared_ptr<ConnectClass>&) { DetachEvent(I_OnPreChangeConnectClass); return MOD_RES_PASSTHRU; }
+void Module::OnChangeConnectClass(LocalUser*, const std::shared_ptr<ConnectClass>&, bool) { DetachEvent(I_OnChangeConnectClass); }
+void Module::OnPostChangeConnectClass(LocalUser*, bool) { DetachEvent(I_OnPostChangeConnectClass); }
ServiceProvider::ServiceProvider(Module* Creator, const std::string& Name, ServiceType Type)
: creator(Creator)
diff --git a/src/modules/m_account.cpp b/src/modules/m_account.cpp
index 3e1795ed5..369ca9efb 100644
--- a/src/modules/m_account.cpp
+++ b/src/modules/m_account.cpp
@@ -324,15 +324,15 @@ public:
return MOD_RES_DENY; // Account required but it does not match.
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
const char* error = nullptr;
- if (stdalgo::string::equalsci(myclass->config->getString("requireaccount"), "nick"))
+ if (stdalgo::string::equalsci(klass->config->getString("requireaccount"), "nick"))
{
if (!accountapi.GetAccountName(user) && !accountapi.IsIdentifiedToNick(user))
error = "an account matching their current nickname";
}
- else if (myclass->config->getBool("requireaccount"))
+ else if (klass->config->getBool("requireaccount"))
{
if (!accountapi.GetAccountName(user))
error = "an account";
@@ -340,8 +340,8 @@ public:
if (error)
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires the user to be logged into %s",
- myclass->GetName().c_str(), error);
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires the user to be logged into %s.",
+ klass->GetName().c_str(), error);
return MOD_RES_DENY;
}
return MOD_RES_PASSTHRU;
diff --git a/src/modules/m_dnsbl.cpp b/src/modules/m_dnsbl.cpp
index 25154d5a2..e0d2d276d 100644
--- a/src/modules/m_dnsbl.cpp
+++ b/src/modules/m_dnsbl.cpp
@@ -504,17 +504,17 @@ public:
}
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
- std::string dnsbl;
- if (!myclass->config->readString("dnsbl", dnsbl))
+ const std::string dnsbl = klass->config->getString("dnsbl");
+ if (!dnsbl.empty())
return MOD_RES_PASSTHRU;
MarkExtItem::List* match = data.markext.Get(user);
if (!match)
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires a DNSBL mark",
- myclass->GetName().c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires a DNSBL mark.",
+ klass->GetName().c_str());
return MOD_RES_DENY;
}
@@ -525,8 +525,8 @@ public:
}
const std::string marks = stdalgo::string::join(dnsbl);
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the DNSBL marks (%s) do not match %s",
- myclass->GetName().c_str(), marks.c_str(), dnsbl.c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the DNSBL marks (%s) do not match %s.",
+ klass->GetName().c_str(), marks.c_str(), dnsbl.c_str());
return MOD_RES_DENY;
}
diff --git a/src/modules/m_gateway.cpp b/src/modules/m_gateway.cpp
index 138894afd..c73c74924 100644
--- a/src/modules/m_gateway.cpp
+++ b/src/modules/m_gateway.cpp
@@ -408,10 +408,10 @@ public:
cmdwebirc.hosts.swap(webirchosts);
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
// If <connect:webirc> is not set then we have nothing to do.
- const std::string webirc = myclass->config->getString("webirc");
+ const std::string webirc = klass->config->getString("webirc");
if (webirc.empty())
return MOD_RES_PASSTHRU;
@@ -420,8 +420,8 @@ public:
const std::string* gateway = cmdwebirc.extban.gateway.Get(user);
if (!gateway)
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires a connection via a WebIRC gateway",
- myclass->GetName().c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires a connection via a WebIRC gateway.",
+ klass->GetName().c_str());
return MOD_RES_DENY;
}
@@ -429,8 +429,8 @@ public:
// allow the check to continue. Otherwise, reject it.
if (!InspIRCd::Match(*gateway, webirc))
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the WebIRC gateway name (%s) does not match %s",
- myclass->GetName().c_str(), gateway->c_str(), webirc.c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the WebIRC gateway name (%s) does not match %s.",
+ klass->GetName().c_str(), gateway->c_str(), webirc.c_str());
return MOD_RES_DENY;
}
diff --git a/src/modules/m_geoclass.cpp b/src/modules/m_geoclass.cpp
index 9f190b1c9..c57073cc5 100644
--- a/src/modules/m_geoclass.cpp
+++ b/src/modules/m_geoclass.cpp
@@ -36,9 +36,9 @@ public:
{
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
- const std::string country = myclass->config->getString("country");
+ const std::string country = klass->config->getString("country");
if (country.empty())
return MOD_RES_PASSTHRU;
@@ -58,8 +58,8 @@ public:
// A list of country codes were specified but the user didn't match
// any of them.
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the origin country (%s) is not any of %s",
- myclass->GetName().c_str(), code.c_str(), country.c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the origin country (%s) is not any of %s.",
+ klass->GetName().c_str(), code.c_str(), country.c_str());
return MOD_RES_DENY;
}
diff --git a/src/modules/m_ident.cpp b/src/modules/m_ident.cpp
index 72db6d399..d6c96e3e6 100644
--- a/src/modules/m_ident.cpp
+++ b/src/modules/m_ident.cpp
@@ -404,12 +404,12 @@ public:
return MOD_RES_PASSTHRU;
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
- if (myclass->config->getBool("requireident") && state.Get(user) != IDENT_FOUND)
+ if (klass->config->getBool("requireident") && state.Get(user) != IDENT_FOUND)
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires an identd response",
- myclass->GetName().c_str());
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires an identd response.",
+ klass->GetName().c_str());
return MOD_RES_DENY;
}
return MOD_RES_PASSTHRU;
diff --git a/src/modules/m_sslinfo.cpp b/src/modules/m_sslinfo.cpp
index 2d2db02f6..812962e83 100644
--- a/src/modules/m_sslinfo.cpp
+++ b/src/modules/m_sslinfo.cpp
@@ -384,17 +384,17 @@ public:
user->WriteNotice(text);
}
- ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
+ ModResult OnPreChangeConnectClass(LocalUser* user, const std::shared_ptr<ConnectClass>& klass) override
{
ssl_cert* cert = cmd.sslapi.GetCertificate(user);
const char* error = nullptr;
- const std::string requiressl = myclass->config->getString("requiressl");
+ const std::string requiressl = klass->config->getString("requiressl");
if (stdalgo::string::equalsci(requiressl, "trusted"))
{
if (!cert || !cert->IsCAVerified())
error = "a trusted TLS client certificate";
}
- else if (myclass->config->getBool("requiressl"))
+ else if (klass->config->getBool("requiressl"))
{
if (!cert)
error = "a TLS connection";
@@ -402,8 +402,8 @@ public:
if (error)
{
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires %s",
- myclass->GetName().c_str(), error);
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires %s.",
+ klass->GetName().c_str(), error);
return MOD_RES_DENY;
}
diff --git a/src/usermanager.cpp b/src/usermanager.cpp
index bcb6e58d3..25fd9317e 100644
--- a/src/usermanager.cpp
+++ b/src/usermanager.cpp
@@ -173,12 +173,8 @@ void UserManager::AddUser(int socket, ListenSocket* via, const irc::sockets::soc
return;
}
- // First class check. We do this again in LocalUser::FullConnect() after DNS is done, and NICK/USER is received.
- New->SetClass();
- // If the user doesn't have an acceptable connect class CheckClass() quits them
- New->CheckClass(ServerInstance->Config->CCOnConnect);
- if (New->quitting)
- return;
+ if (!New->FindConnectClass())
+ return; // User does not match any connect classes.
/*
* even with bancache, we still have to keep User::exempt current.
@@ -296,6 +292,8 @@ void UserManager::QuitUser(User* user, const std::string& quitmessage, const std
user->GetIPString().c_str(), operquitmsg.c_str());
}
local_users.erase(lu);
+ if (lu->GetClass())
+ lu->GetClass()->use_count--;
}
if (!clientlist.erase(user->nick))
diff --git a/src/users.cpp b/src/users.cpp
index 869f6b281..6c89669e0 100644
--- a/src/users.cpp
+++ b/src/users.cpp
@@ -378,51 +378,6 @@ void User::OperLogout()
FOREACH_MOD(OnPostOperLogout, (this, account));
}
-/*
- * Check class restrictions
- */
-void LocalUser::CheckClass(bool clone_count)
-{
- const ConnectClass::Ptr& a = GetClass();
- if (!a)
- {
- ServerInstance->Users.QuitUser(this, "Access denied by configuration");
- return;
- }
- else if (a->type == ConnectClass::DENY)
- {
- ServerInstance->Users.QuitUser(this, a->config->getString("reason", "Unauthorised connection", 1));
- return;
- }
- else if (clone_count)
- {
- const UserManager::CloneCounts& clonecounts = ServerInstance->Users.GetCloneCounts(this);
- if (a->maxlocal && clonecounts.local > a->maxlocal)
- {
- ServerInstance->Users.QuitUser(this, "No more connections allowed from your host via this connect class (local)");
- if (a->maxconnwarn)
- {
- ServerInstance->SNO.WriteToSnoMask('a', "WARNING: maximum local connections for the %s class (%ld) exceeded by %s",
- a->name.c_str(), a->maxlocal, this->GetIPString().c_str());
- }
- return;
- }
- else if (a->maxglobal && clonecounts.global > a->maxglobal)
- {
- ServerInstance->Users.QuitUser(this, "No more connections allowed from your host via this connect class (global)");
- if (a->maxconnwarn)
- {
- ServerInstance->SNO.WriteToSnoMask('a', "WARNING: maximum global connections for the %s class (%ld) exceeded by %s",
- a->name.c_str(), a->maxglobal, this->GetIPString().c_str());
- }
- return;
- }
- }
-
- this->nextping = ServerInstance->Time() + a->pingtime;
- this->uniqueusername = a->uniqueusername;
-}
-
bool LocalUser::CheckLines(bool doZline)
{
const char* check[] = { "G" , "K", (doZline) ? "Z" : nullptr, nullptr };
@@ -455,11 +410,10 @@ void LocalUser::FullConnect()
* may put the user into a totally separate class with different restrictions! so we *must* check again.
* Don't remove this! -- w00t
*/
- connectclass = nullptr;
- SetClass();
- CheckClass();
- CheckLines();
+ if (!FindConnectClass())
+ return; // User does not match any connect classes.
+ CheckLines();
if (quitting)
return;
@@ -651,14 +605,71 @@ void LocalUser::ChangeRemoteAddress(const irc::sockets::sockaddrs& sa)
ServerInstance->Users.AddClone(this);
// Recheck the connect class.
- this->connectclass = nullptr;
- this->SetClass();
- this->CheckClass();
-
- if (!quitting)
+ if (FindConnectClass())
FOREACH_MOD(OnChangeRemoteAddress, (this));
}
+bool LocalUser::FindConnectClass()
+{
+ ServerInstance->Logs.Debug("CONNECTCLASS", "Finding a connect class for %s (%s) ...",
+ uuid.c_str(), GetFullRealHost().c_str());
+
+ for (const auto& klass : ServerInstance->Config->Classes)
+ {
+ ServerInstance->Logs.Debug("CONNECTCLASS", "Checking the %s connect class ...",
+ klass->GetName().c_str());
+
+ // Users can not be automatically assigned to a named class.
+ if (klass->type == ConnectClass::NAMED)
+ {
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as neither <connect:allow> nor <connect:deny> are set.",
+ klass->GetName().c_str());
+ continue;
+ }
+
+ ModResult modres;
+ FIRST_MOD_RESULT(OnPreChangeConnectClass, modres, (this, klass));
+ if (modres != MOD_RES_DENY)
+ {
+ ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is suitable for %s (%s).",
+ klass->GetName().c_str(), uuid.c_str(), GetFullRealHost().c_str());
+
+ ChangeConnectClass(klass, false);
+ return !quitting;
+ }
+ }
+
+ // The user didn't match any connect classes.
+ if (connectclass)
+ {
+ connectclass->use_count--;
+ connectclass = nullptr;
+ }
+ ServerInstance->Users.QuitUser(this, "You are not allowed to connect to this server");
+ return false;
+}
+
+void LocalUser::ChangeConnectClass(const std::shared_ptr<ConnectClass>& klass, bool force)
+{
+ // Let modules know the class is about to be changed.
+ FOREACH_MOD(OnChangeConnectClass, (this, klass, force));
+ if (quitting)
+ return; // User hit some kind of restriction.
+
+ // Assign the new connect class.
+ if (connectclass)
+ connectclass->use_count--;
+ connectclass = klass;
+ connectclass->use_count++;
+
+ // Update the core user data that depends on connect class.
+ nextping = ServerInstance->Time() + klass->pingtime;
+ uniqueusername = klass->uniqueusername;
+
+ // Let modules know the class has been changed.
+ FOREACH_MOD(OnPostChangeConnectClass, (this, force));
+}
+
void LocalUser::Write(const ClientProtocol::SerializedMessage& text)
{
if (!eh.HasFd())
@@ -952,126 +963,6 @@ bool User::ChangeIdent(const std::string& newident)
return true;
}
-/*
- * Sets a user's connection class.
- * If the class name is provided, it will be used. Otherwise, the class will be guessed using host/ip/ident/etc.
- * NOTE: If the <ALLOW> or <DENY> tag specifies an ip, and this user resolves,
- * then their ip will be taken as 'priority' anyway, so for example,
- * <connect allow="127.0.0.1"> will match joe!bloggs@localhost
- */
-void LocalUser::SetClass(const std::string& explicit_name)
-{
- ServerInstance->Logs.Debug("CONNECTCLASS", "Setting connect class for %s (%s) ...",
- this->uuid.c_str(), this->GetFullRealHost().c_str());
-
- ConnectClass::Ptr found;
- if (!explicit_name.empty())
- {
- for (const auto& c : ServerInstance->Config->Classes)
- {
- if (explicit_name == c->name)
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "Connect class explicitly set to %s",
- explicit_name.c_str());
- found = c;
- }
- }
- }
- else
- {
- for (const auto& c : ServerInstance->Config->Classes)
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "Checking the %s connect class ...",
- c->GetName().c_str());
-
- ModResult MOD_RESULT;
- FIRST_MOD_RESULT(OnSetConnectClass, MOD_RESULT, (this, c));
- if (MOD_RESULT == MOD_RES_DENY)
- continue;
-
- if (MOD_RESULT == MOD_RES_ALLOW)
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class was explicitly chosen by a module",
- c->GetName().c_str());
- found = c;
- break;
- }
-
- if (c->type == ConnectClass::NAMED)
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as neither <connect:allow> nor <connect:deny> are set",
- c->GetName().c_str());
- continue;
- }
-
- bool conndone = connected != User::CONN_NONE;
- if (c->config->getBool("connected", c->config->getBool("registered", conndone)) != conndone)
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it requires that the user is %s",
- c->GetName().c_str(), conndone ? "not fully connected" : "fully connected");
- continue;
- }
-
- bool hostmatches = false;
- for (const auto& host : c->GetHosts())
- {
- if (InspIRCd::MatchCIDR(this->GetIPString(), host) || InspIRCd::MatchCIDR(this->GetRealHost(), host))
- {
- hostmatches = true;
- break;
- }
- }
- if (!hostmatches)
- {
- const std::string hosts = stdalgo::string::join(c->GetHosts());
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as neither the host (%s) nor the IP (%s) matches %s",
- c->GetName().c_str(), this->GetRealHost().c_str(), this->GetIPString().c_str(), hosts.c_str());
- continue;
- }
-
- /*
- * deny change if change will take class over the limit check it HERE, not after we found a matching class,
- * because we should attempt to find another class if this one doesn't match us. -- w00t
- */
- if (c->limit && (c.use_count() >= static_cast<long>(c->limit)))
- {
- // HACK: using use_count() is awful and should be removed before v4 is released.
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as it has reached its user limit (%lu)",
- c->GetName().c_str(), c->limit);
- continue;
- }
-
- /* if it requires a port and our port doesn't match, fail */
- if (!c->ports.empty() && !c->ports.count(this->server_sa.port()))
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as the connection port (%hu) is not any of %s",
- c->GetName().c_str(), this->server_sa.port(), stdalgo::string::join(c->ports).c_str());
- continue;
- }
-
- if (conndone && !c->password.empty() && !ServerInstance->PassCompare(c->password, password, c->passwordhash))
- {
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is not suitable as requires a password and %s",
- c->GetName().c_str(), password.empty() ? "one was not provided" : "the provided password was incorrect");
- continue;
- }
-
- /* we stop at the first class that meets ALL criteria. */
- ServerInstance->Logs.Debug("CONNECTCLASS", "The %s connect class is suitable for %s (%s)",
- c->GetName().c_str(), this->uuid.c_str(), this->GetFullRealHost().c_str());
- found = c;
- break;
- }
- }
-
- /*
- * Okay, assuming we found a class that matches.. switch us into that class, keeping refcounts up to date.
- */
- if (found)
- {
- connectclass = found;
- }
-}
void User::PurgeEmptyChannels()
{
@@ -1116,7 +1007,7 @@ ConnectClass::ConnectClass(std::shared_ptr<ConfigTag> tag, Type t, const std::ve
{
}
-ConnectClass::ConnectClass(std::shared_ptr<ConfigTag> tag, Type t, const std::vector<std::string>& masks, const ConnectClass::Ptr& parent)
+ConnectClass::ConnectClass(std::shared_ptr<ConfigTag> tag, Type t, const std::vector<std::string>& masks, const std::shared_ptr<ConnectClass>& parent)
{
Update(parent);
name = "unnamed";
@@ -1170,8 +1061,8 @@ void ConnectClass::Configure(const std::string& classname, std::shared_ptr<Confi
limit = tag->getUInt("limit", limit, 1);
maxchans = tag->getUInt("maxchans", maxchans);
maxconnwarn = tag->getBool("maxconnwarn", maxconnwarn);
- maxglobal = tag->getUInt("globalmax", maxglobal, 1);
- maxlocal = tag->getUInt("localmax", maxlocal, 1);
+ maxlocal = tag->getUInt("localmax", maxlocal);
+ maxglobal = tag->getUInt("globalmax", maxglobal, maxlocal);
penaltythreshold = tag->getUInt("threshold", penaltythreshold, 1);
pingtime = tag->getDuration("pingfreq", pingtime);
recvqmax = tag->getUInt("recvq", recvqmax, ServerInstance->Config->Limits.MaxLine);
@@ -1181,7 +1072,7 @@ void ConnectClass::Configure(const std::string& classname, std::shared_ptr<Confi
uniqueusername = tag->getBool("uniqueusername", uniqueusername);
}
-void ConnectClass::Update(const ConnectClass::Ptr& src)
+void ConnectClass::Update(const std::shared_ptr<ConnectClass>& src)
{
ServerInstance->Logs.Debug("CONNECTCLASS", "Updating %s from %s", name.c_str(), src->name.c_str());
commandrate = src->commandrate;