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 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 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& oper); + virtual ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr& 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& oper); + virtual void OnOperLogin(User* user, const std::shared_ptr& 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& o, const std::shared_ptr& 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& account, bool force = false); + bool OperLogin(const std::shared_ptr& 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& 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& 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&) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; } -void Module::OnOperLogin(User*, const std::shared_ptr&) { DetachEvent(I_OnOperLogin); } -void Module::OnPostOperLogin(User*) { DetachEvent(I_OnPostOperLogin); } +ModResult Module::OnPreOperLogin(LocalUser*, const std::shared_ptr&, bool) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; } +void Module::OnOperLogin(User*, const std::shared_ptr&, 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&) { 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& oper) override + ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr& 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& oper) +void ModuleSpanningTree::OnOperLogin(User* user, const std::shared_ptr& 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& oper) override; + void OnOperLogin(User* user, const std::shared_ptr& 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& oper) override + ModResult OnPreOperLogin(LocalUser* user, const std::shared_ptr& 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& account, bool force) +bool User::OperLogin(const std::shared_ptr& 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& 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& 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& , 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& 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);