Rework how users are assigned to connect classes.

- Move core connect class checks and <performance:clonesonconnect>
  to the core_user module.
- Add pre-change and post-change events for when a connect class
  changes.
- Split explicit class changing out into its own method.
- Remove the need to almost always call CheckClass after SetClass.
- Add use counting to the connect class instead of relying on the
  shared_ptr use count.
This commit is contained in:
Sadie Powell 2023-01-08 16:12:43 +00:00
parent 94740c6252
commit 8831595e1a
18 changed files with 263 additions and 258 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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. */

View File

@ -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

View File

@ -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);
}
};

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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))

View File

@ -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;