diff options
| -rw-r--r-- | include/configreader.h | 9 | ||||
| -rw-r--r-- | include/modules.h | 31 | ||||
| -rw-r--r-- | include/modules/isupport.h | 2 | ||||
| -rw-r--r-- | include/users.h | 34 | ||||
| -rw-r--r-- | src/configreader.cpp | 7 | ||||
| -rw-r--r-- | src/coremods/core_channel/core_channel.cpp | 2 | ||||
| -rw-r--r-- | src/coremods/core_info/core_info.h | 4 | ||||
| -rw-r--r-- | src/coremods/core_oper/core_oper.cpp | 15 | ||||
| -rw-r--r-- | src/coremods/core_user/core_user.cpp | 94 | ||||
| -rw-r--r-- | src/modules.cpp | 4 | ||||
| -rw-r--r-- | src/modules/m_account.cpp | 10 | ||||
| -rw-r--r-- | src/modules/m_dnsbl.cpp | 14 | ||||
| -rw-r--r-- | src/modules/m_gateway.cpp | 12 | ||||
| -rw-r--r-- | src/modules/m_geoclass.cpp | 8 | ||||
| -rw-r--r-- | src/modules/m_ident.cpp | 8 | ||||
| -rw-r--r-- | src/modules/m_sslinfo.cpp | 10 | ||||
| -rw-r--r-- | src/usermanager.cpp | 10 | ||||
| -rw-r--r-- | src/users.cpp | 247 |
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; |
