From 452e62c05021b29b4f0641245cafa6ae115e2198 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 11 Apr 2025 12:05:18 +0100 Subject: [PATCH] Add support for local password comparison in sql_authentication. --- data/modules.example.conf | 17 ++++++++++++++ modules/sql_authentication.cpp | 43 ++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/data/modules.example.conf b/data/modules.example.conf index a242c0a6d..58c167c86 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -670,6 +670,23 @@ module */ query = "SELECT `email_addr` AS `email` FROM `my_users` WHERE `username` = @a@ AND `password` = MD5(CONCAT('salt', @p@))" + /* + * If your database uses a password hashing algorithm that can not be compared using a simple + * comparison function then you can specify it here to compare locally. + * + * You will need to have the appropriate encryption module (e.g. enc_bcrypt) loaded in order + * for this to work. + */ + #password_hash = "bcrypt" + + /* + * If using the password_hash field (above) you will need to specify the name of the field to + * fetch the password from. + * + * Defaults to "password" if not set. + */ + #password_field = "password" + /* * If set, the reason to give the users who try to "/msg NickServ REGISTER". * If not set, then registration is not blocked. diff --git a/modules/sql_authentication.cpp b/modules/sql_authentication.cpp index a524a43fc..9c3605ed9 100644 --- a/modules/sql_authentication.cpp +++ b/modules/sql_authentication.cpp @@ -7,16 +7,29 @@ */ #include "module.h" +#include "modules/encryption.h" #include "modules/sql.h" -static Module *me; +namespace +{ + Module *me; + ServiceReference encryption; + Anope::string password_hash, password_field; +} class SQLAuthenticationResult final : public SQL::Interface { Reference user; IdentifyRequest *req; + void OnFail(const Anope::string &reason) + { + Log(LOG_DEBUG) << "sql_authentication: Unsuccessful authentication for " << req->GetAccount() << ": " << reason; + delete this; + return; + } + public: SQLAuthenticationResult(User *u, IdentifyRequest *r) : SQL::Interface(me), user(u), req(r) { @@ -31,10 +44,25 @@ public: void OnResult(const SQL::Result &r) override { if (r.Rows() == 0) + return OnFail("no rows"); + + if (!password_hash.empty()) { - Log(LOG_DEBUG) << "sql_authentication: Unsuccessful authentication for " << req->GetAccount(); - delete this; - return; + if (!encryption) + return OnFail("encryption provider not loaded"); + + auto success = false; + for (auto i = 0; i < r.Rows(); ++i) + { + if (encryption->Compare(r.Get(i, password_field), req->GetPassword())) + { + success = true; + break; + } + } + + if (!success) + return OnFail("no matching passwords"); } Log(LOG_DEBUG) << "sql_authentication: Successful authentication for " << req->GetAccount(); @@ -99,6 +127,13 @@ public: this->disable_email_reason = config.Get("disable_email_reason"); this->SQL = ServiceReference("SQL::Provider", this->engine); + + password_hash = config.Get("password_hash"); + if (!password_hash.empty()) + { + password_field = config.Get("password_field", "password"); + encryption = ServiceReference("Encryption::Provider", password_hash); + } } EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override