aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Sadie Powell2022-12-11 09:58:27 +0000
committerGravatar Sadie Powell2022-12-11 10:14:33 +0000
commit3c6e24665f52e3a38adfe4ab6a9adcbbb4926ca2 (patch)
treeb2b51956059bd7636aeeb81233aff05983c7687e
parentImprove the oper login flow and error messages. (diff)
Move <oper:autologin> from m_sslinfo to core_oper and rework.
- Promote autologin to a core concept with visibility in events. - Replace the binary yes/no value with strict/relaxed/never. This intentionally breaks v3 oper block autologin as admins will need to review them for the security implications of the new behaviour.
-rw-r--r--docs/conf/opers.conf.example16
-rw-r--r--include/modules.h9
-rw-r--r--include/users.h25
-rw-r--r--src/coremods/core_oper/core_oper.cpp34
-rw-r--r--src/modules.cpp6
-rw-r--r--src/modules/m_account.cpp9
-rw-r--r--src/modules/m_operjoin.cpp2
-rw-r--r--src/modules/m_opermodes.cpp2
-rw-r--r--src/modules/m_opermotd.cpp2
-rw-r--r--src/modules/m_operprefix.cpp2
-rw-r--r--src/modules/m_spanningtree/main.cpp2
-rw-r--r--src/modules/m_spanningtree/main.h2
-rw-r--r--src/modules/m_spanningtree/opertype.cpp5
-rw-r--r--src/modules/m_sslinfo.cpp30
-rw-r--r--src/modules/m_swhois.cpp2
-rw-r--r--src/users.cpp32
16 files changed, 126 insertions, 54 deletions
diff --git a/docs/conf/opers.conf.example b/docs/conf/opers.conf.example
index 8e065c671..110aa5c6e 100644
--- a/docs/conf/opers.conf.example
+++ b/docs/conf/opers.conf.example
@@ -142,10 +142,18 @@
# If the sslinfo module isn't loaded, this option will be ignored.
#fingerprint="67cb9dc013248a829bb2171ed11becd4"
- # autologin: If a TLS client certificate fingerprint for this oper is specified,
- # you can have the oper block automatically log in if they match the fingerprint
- # and host fields. Requires the sslinfo module.
- #autologin="yes"
+ # autologin: Whether to automatically log this server operator in on connect if all
+ # of their details match the ones in this <oper> block. Can be set to "strict" to
+ # automatically log in if the user's nickname matches the oper account name and the
+ # account/host/sslonly/etc fields match, "relaxed" to automatically log in if the
+ # account/host/sslonly/etc fields match, and "never" to not allow automatically
+ # logging in to this oper account. Defaults to "never".
+ #
+ # IMPORTANT: As this option overrides the password field it should **NOT** be used
+ # unless you are certain that nobody other than the intended user will match the
+ # restrictions of this <oper> block. Failure to do this may result in your server
+ # being compromised.
+ #autologin="strict"
# sslonly: If enabled, this oper can only oper up if they're using a TLS connection.
# Setting this option adds a decent bit of security. Highly recommended
diff --git a/include/modules.h b/include/modules.h
index aef8e47e4..ef1e5e22c 100644
--- a/include/modules.h
+++ b/include/modules.h
@@ -925,21 +925,24 @@ public:
/** Called when a local user is attempting to log in to an server operator account.
* @param user The user who is attempting to log in.
* @param oper The server operator account they are attempting to log in to.
+ * @param automatic Whether the login attempt is being performed automatically.
* @return MOD_RES_ALLOW to explicitly allow the login, MOD_RES_DENY to explicitly deny the
* login, or MOD_RES_PASSTHRU to let another module handle the event.
*/
- virtual ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper);
+ virtual ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper, bool automatic);
/** Called when a user is about to be logged in to an server operator account.
* @param user The user who is about to be logged in.
* @param oper The server operator account they are logging in to.
+ * @param automatic Whether the login was performed automatically.
*/
- virtual void OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper);
+ virtual void OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper, bool automatic);
/** Called after a user has been logged in to an server operator account.
* @param user The user who has been logged in.
+ * @param automatic Whether the login was performed automatically.
*/
- virtual void OnPostOperLogin(User* user);
+ virtual void OnPostOperLogin(User* user, bool automatic);
/** Called when a user is about to be logged out of an server operator account.
* @param user The user who is about to be logged out.
diff --git a/include/users.h b/include/users.h
index 1355fc8f8..7473a4ae5 100644
--- a/include/users.h
+++ b/include/users.h
@@ -236,7 +236,24 @@ public:
class CoreExport OperAccount
: public OperType
{
-private:
+protected:
+ /** Possible states for whether an oper account can be automatically logged into. */
+ enum class AutoLogin
+ : uint8_t
+ {
+ /** Users can automatically log in to this account if they match all fields and their nick matches the account name. */
+ STRICT,
+
+ /** Users can automatically log in to this account if they match all fields. */
+ RELAXED,
+
+ /** Users can not automatically log in to this account. */
+ NEVER,
+ };
+
+ /** Whether this oper account can be automatically logged into. */
+ AutoLogin autologin;
+
/** The password to used to log into this oper account. */
std::string password;
@@ -254,6 +271,9 @@ public:
*/
OperAccount(const std::string& n, const std::shared_ptr<OperType>& o, const std::shared_ptr<ConfigTag>& t);
+ /** Check whether this user can attempt to automatically log in to this account. */
+ bool CanAutoLogin(LocalUser* user) const;
+
/** Check the specified password against the one from this oper account's password.
* @param pw The password to check.
*/
@@ -581,10 +601,11 @@ public:
/** Logs this user into the specified server operator account.
* @param account The account to log this user in to.
+ * @param automatic Whether this is an automatic login attempt.
* @param force Whether to ignore any checks from OnPreOperLogin.
* @return True if the user is logged into successfully; otherwise, false.
*/
- bool OperLogin(const std::shared_ptr<OperAccount>& account, bool force = false);
+ bool OperLogin(const std::shared_ptr<OperAccount>& account, bool automatic = false, bool force = false);
/** Logs this user out of their server operator account. Does nothing to non-operators. */
void OperLogout();
diff --git a/src/coremods/core_oper/core_oper.cpp b/src/coremods/core_oper/core_oper.cpp
index 9c52e286b..c08a0b5f3 100644
--- a/src/coremods/core_oper/core_oper.cpp
+++ b/src/coremods/core_oper/core_oper.cpp
@@ -63,18 +63,38 @@ public:
cmdkill.hideservicekills = security->getBool("hideservicekills", security->getBool("hideulinekills"));
}
- ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper) override
+ void OnPostConnect(User* user) override
+ {
+ LocalUser* luser = IS_LOCAL(user);
+ if (!luser)
+ return;
+
+ // Find an auto-oper block for this user.
+ for (const auto& [_, account] : ServerInstance->Config->OperAccounts)
+ {
+ if (!account->CanAutoLogin(luser))
+ continue; // No autologin for this account.
+
+ if (user->OperLogin(account, true))
+ break; // Successfully logged in to the account.
+ }
+ }
+
+ ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper, bool automatic) override
{
const std::string hosts = oper->GetConfig()->getString("host");
if (InspIRCd::MatchMask(hosts, user->MakeHost(), user->MakeHostIP()))
return MOD_RES_PASSTHRU; // Host matches.
- ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are connecting from the wrong user@host.",
- user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ if (!automatic)
+ {
+ ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are connecting from the wrong user@host.",
+ user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ }
return MOD_RES_DENY; // Host does not match.
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
LocalUser* luser = IS_LOCAL(user);
if (!luser)
@@ -84,9 +104,9 @@ public:
strchr("AEIOUaeiou", user->oper->GetType()[0]) ? "an" : "a",
user->oper->GetType().c_str()));
- ServerInstance->SNO.WriteToSnoMask('o', "%s (%s) is now a server operator of type %s (using account %s)",
- user->nick.c_str(), user->MakeHost().c_str(), user->oper->GetType().c_str(),
- user->oper->GetName().c_str());
+ ServerInstance->SNO.WriteToSnoMask('o', "%s (%s) [%s] is now a server operator of type \x02%s\x02 (%susing account \x02%s\x02).",
+ user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), user->oper->GetType().c_str(),
+ automatic ? "automatically " : "", user->oper->GetName().c_str());
const std::string vhost = luser->oper->GetConfig()->getString("vhost");
if (!vhost.empty())
diff --git a/src/modules.cpp b/src/modules.cpp
index 45b8a4538..ed354a680 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -162,9 +162,9 @@ void Module::OnServiceAdd(ServiceProvider&) { DetachEvent(I_OnServiceAdd); }
void Module::OnServiceDel(ServiceProvider&) { DetachEvent(I_OnServiceDel); }
ModResult Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; }
void Module::OnShutdown(const std::string& reason) { DetachEvent(I_OnShutdown); }
-ModResult Module::OnPreOperLogin(LocalUser*, const std::shared_ptr<OperAccount>&) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; }
-void Module::OnOperLogin(User*, const std::shared_ptr<OperAccount>&) { DetachEvent(I_OnOperLogin); }
-void Module::OnPostOperLogin(User*) { DetachEvent(I_OnPostOperLogin); }
+ModResult Module::OnPreOperLogin(LocalUser*, const std::shared_ptr<OperAccount>&, bool) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; }
+void Module::OnOperLogin(User*, const std::shared_ptr<OperAccount>&, bool) { DetachEvent(I_OnOperLogin); }
+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); }
diff --git a/src/modules/m_account.cpp b/src/modules/m_account.cpp
index d9251f04f..6d408ba39 100644
--- a/src/modules/m_account.cpp
+++ b/src/modules/m_account.cpp
@@ -338,7 +338,7 @@ public:
return MOD_RES_PASSTHRU;
}
- ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper) override
+ ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper, bool automatic) override
{
const std::string accountstr = oper->GetConfig()->getString("account");
if (accountstr.empty())
@@ -357,8 +357,11 @@ public:
return MOD_RES_PASSTHRU; // Matches on account name.
}
- ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not logged into the correct user account.",
- user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ if (!automatic)
+ {
+ ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not logged into the correct user account.",
+ user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ }
return MOD_RES_DENY; // Account required but it does not match.
}
diff --git a/src/modules/m_operjoin.cpp b/src/modules/m_operjoin.cpp
index 2608dcca3..3b4c329ad 100644
--- a/src/modules/m_operjoin.cpp
+++ b/src/modules/m_operjoin.cpp
@@ -52,7 +52,7 @@ public:
operChans.push_back(channame);
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
LocalUser* localuser = IS_LOCAL(user);
if (!localuser)
diff --git a/src/modules/m_opermodes.cpp b/src/modules/m_opermodes.cpp
index e0911986c..5876cfef2 100644
--- a/src/modules/m_opermodes.cpp
+++ b/src/modules/m_opermodes.cpp
@@ -35,7 +35,7 @@ public:
{
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
if (!IS_LOCAL(user))
return; // We don't handle remote users.
diff --git a/src/modules/m_opermotd.cpp b/src/modules/m_opermotd.cpp
index f109d43ee..7332f545b 100644
--- a/src/modules/m_opermotd.cpp
+++ b/src/modules/m_opermotd.cpp
@@ -136,7 +136,7 @@ public:
{
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
if (IS_LOCAL(user) && user->oper->GetConfig()->getBool("automotd", onoper))
cmd.ShowOperMOTD(user, false);
diff --git a/src/modules/m_operprefix.cpp b/src/modules/m_operprefix.cpp
index a3fae72e4..4540e63a4 100644
--- a/src/modules/m_operprefix.cpp
+++ b/src/modules/m_operprefix.cpp
@@ -104,7 +104,7 @@ public:
ServerInstance->Modes.Process(ServerInstance->FakeClient, memb->chan, nullptr, changelist);
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
if (IS_LOCAL(user) && (!user->IsModeSet(hideopermode)))
SetOperPrefix(user, true);
diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp
index f84e1c91a..584b70b5e 100644
--- a/src/modules/m_spanningtree/main.cpp
+++ b/src/modules/m_spanningtree/main.cpp
@@ -772,7 +772,7 @@ restart:
}
}
-void ModuleSpanningTree::OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper)
+void ModuleSpanningTree::OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper, bool automatic)
{
if (!user->IsFullyConnected() || !IS_LOCAL(user))
return;
diff --git a/src/modules/m_spanningtree/main.h b/src/modules/m_spanningtree/main.h
index 539803a68..e1dc83379 100644
--- a/src/modules/m_spanningtree/main.h
+++ b/src/modules/m_spanningtree/main.h
@@ -196,7 +196,7 @@ public:
void OnUserKick(User* source, Membership* memb, const std::string& reason, CUList& excepts) override;
void OnPreRehash(User* user, const std::string& parameter) override;
void ReadConfig(ConfigStatus& status) override;
- void OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper) override;
+ void OnOperLogin(User* user, const std::shared_ptr<OperAccount>& oper, bool automatic) override;
void OnAddLine(User* u, XLine* x) override;
void OnDelLine(User* u, XLine* x) override;
ModResult OnStats(Stats::Context& stats) override;
diff --git a/src/modules/m_spanningtree/opertype.cpp b/src/modules/m_spanningtree/opertype.cpp
index 0dcfdc4d4..2babaffe8 100644
--- a/src/modules/m_spanningtree/opertype.cpp
+++ b/src/modules/m_spanningtree/opertype.cpp
@@ -49,8 +49,9 @@ CmdResult CommandOpertype::HandleRemote(RemoteUser* u, CommandBase::Params& para
return CmdResult::SUCCESS;
}
- ServerInstance->SNO.WriteToSnoMask('O', "From %s: %s (%s) is now a server operator of type %s",
- u->server->GetName().c_str(), u->nick.c_str(), u->MakeHost().c_str(), u->oper->GetType().c_str());
+ ServerInstance->SNO.WriteToSnoMask('O', "From %s: %s (%s) [%s] is now a server operator of type \x02%s\x02.",
+ u->server->GetName().c_str(), u->nick.c_str(), u->MakeHost().c_str(), u->GetIPString().c_str(),
+ u->oper->GetType().c_str());
return CmdResult::SUCCESS;
}
diff --git a/src/modules/m_sslinfo.cpp b/src/modules/m_sslinfo.cpp
index d89f74006..bd47dc676 100644
--- a/src/modules/m_sslinfo.cpp
+++ b/src/modules/m_sslinfo.cpp
@@ -323,21 +323,27 @@ public:
return MOD_RES_PASSTHRU;
}
- ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper) override
+ ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr<OperAccount>& oper, bool automatic) override
{
auto cert = cmd.sslapi.GetCertificate(user);
if (oper->GetConfig()->getBool("sslonly") && !cert)
{
- ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not connected using TLS.",
- user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ if (!automatic)
+ {
+ ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not connected using TLS.",
+ user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ }
return MOD_RES_DENY;
}
const std::string fingerprint = oper->GetConfig()->getString("fingerprint");
if (!fingerprint.empty() && (!cert || !MatchFP(cert, fingerprint)))
{
- ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not using the correct TLS client certificate.",
- user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ if (!automatic)
+ {
+ ServerInstance->SNO.WriteGlobalSno('o', "%s (%s) [%s] failed to log into the \x02%s\x02 oper account because they are not using the correct TLS client certificate.",
+ user->nick.c_str(), user->MakeHost().c_str(), user->GetIPString().c_str(), oper->GetName().c_str());
+ }
return MOD_RES_DENY;
}
@@ -365,20 +371,6 @@ public:
if (cert && !cert->GetFingerprint().empty())
text.append(" and your TLS client certificate fingerprint is ").append(cert->GetFingerprint());
user->WriteNotice(text);
-
- if (!cert)
- return;
-
- // Find an auto-oper block for this user
- for (const auto& [_, info] : ServerInstance->Config->OperAccounts)
- {
- const auto& oper = info->GetConfig();
- if (!oper->getBool("autologin"))
- continue; // No autologin for this block.
-
- if (!user->OperLogin(info))
- continue; // Some other field does not match.
- }
}
ModResult OnSetConnectClass(LocalUser* user, const ConnectClass::Ptr& myclass) override
diff --git a/src/modules/m_swhois.cpp b/src/modules/m_swhois.cpp
index 06e101746..bd5ae99c3 100644
--- a/src/modules/m_swhois.cpp
+++ b/src/modules/m_swhois.cpp
@@ -116,7 +116,7 @@ public:
return MOD_RES_PASSTHRU;
}
- void OnPostOperLogin(User* user) override
+ void OnPostOperLogin(User* user, bool automatic) override
{
if (!IS_LOCAL(user))
return;
diff --git a/src/users.cpp b/src/users.cpp
index cfa2fe305..0c12dc2fa 100644
--- a/src/users.cpp
+++ b/src/users.cpp
@@ -349,13 +349,13 @@ Cullable::Result FakeUser::Cull()
return User::Cull();
}
-bool User::OperLogin(const std::shared_ptr<OperAccount>& account, bool force)
+bool User::OperLogin(const std::shared_ptr<OperAccount>& account, bool automatic, bool force)
{
LocalUser* luser = IS_LOCAL(this);
if (luser && !quitting && !force)
{
ModResult modres;
- FIRST_MOD_RESULT(OnPreOperLogin, modres, (luser, account));
+ FIRST_MOD_RESULT(OnPreOperLogin, modres, (luser, account, automatic));
if (modres == MOD_RES_DENY)
return false; // Module rejected the oper attempt.
}
@@ -364,7 +364,7 @@ bool User::OperLogin(const std::shared_ptr<OperAccount>& account, bool force)
if (IsOper())
OperLogout();
- FOREACH_MOD(OnOperLogin, (this, account));
+ FOREACH_MOD(OnOperLogin, (this, account, automatic));
// When a user logs in we need to:
// 1. Set the operator account (this is what IsOper checks).
@@ -385,7 +385,7 @@ bool User::OperLogin(const std::shared_ptr<OperAccount>& account, bool force)
}
ServerInstance->Users.all_opers.push_back(this);
- FOREACH_MOD(OnPostOperLogin, (this));
+ FOREACH_MOD(OnPostOperLogin, (this, automatic));
return true;
}
@@ -1379,6 +1379,12 @@ OperAccount::OperAccount(const std::string& n, const std::shared_ptr<OperType>&
, passwordhash(t->getString("hash", "plaintext", 1))
, type(o ? o->GetName() : n)
{
+ autologin = t->getEnum("autologin", AutoLogin::NEVER, {
+ { "strict", AutoLogin::STRICT },
+ { "relaxed", AutoLogin::RELAXED },
+ { "never", AutoLogin::NEVER },
+ });
+
if (o)
{
chanmodes = o->chanmodes;
@@ -1391,6 +1397,24 @@ OperAccount::OperAccount(const std::string& n, const std::shared_ptr<OperType>&
Configure(t, true);
}
+bool OperAccount::CanAutoLogin(LocalUser* user) const
+{
+ switch (autologin)
+ {
+ case AutoLogin::STRICT:
+ return user->nick == GetName();
+
+ case AutoLogin::RELAXED:
+ return true;
+
+ case AutoLogin::NEVER:
+ return false;
+ }
+
+ // Should never be reached.
+ return false;
+}
+
bool OperAccount::CheckPassword(const std::string& pw) const
{
return ServerInstance->PassCompare(password, pw, passwordhash);