mirror of
https://github.com/inspircd/inspircd.git
synced 2025-03-09 18:49:03 -04:00
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:
parent
94740c6252
commit
8831595e1a
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
247
src/users.cpp
247
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user