From 449cfa65034ba14739a8be41781d6531f1975674 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Tue, 26 May 2026 09:29:57 +0100 Subject: [PATCH 1/4] Add EscapeDN and EscapeSF to the LDAP API. --- include/modules/ldap.h | 10 ++++++ modules/extra/m_ldap.cpp | 74 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/include/modules/ldap.h b/include/modules/ldap.h index 3caf7ac1a..ae6825efa 100644 --- a/include/modules/ldap.h +++ b/include/modules/ldap.h @@ -166,6 +166,16 @@ class LDAPProvider : public Service * @param attributes The attributes to modify */ virtual void Modify(LDAPInterface *i, const Anope::string &base, LDAPMods &attributes) = 0; + + /** Escapes a LDAP string for use in a DN. + * @param str The string to escape. + */ + virtual Anope::string EscapeDN(const Anope::string &str) const = 0; + + /** Escapes a LDAP string for use in a search filter. + * @param str The string to escape. + */ + virtual Anope::string EscapeSF(const Anope::string &str) const = 0; }; #endif // ANOPE_LDAP_H diff --git a/modules/extra/m_ldap.cpp b/modules/extra/m_ldap.cpp index fab11bdfd..27079eb88 100644 --- a/modules/extra/m_ldap.cpp +++ b/modules/extra/m_ldap.cpp @@ -388,6 +388,80 @@ class LDAPService : public LDAPProvider, public Thread, public Condition QueueRequest(mod); } + Anope::string EscapeDN(const Anope::string &str) const anope_override + { + if (str.empty()) + return str; + + Anope::string newstr; + newstr.str().reserve(str.length()); + for (size_t idx = 0; idx < str.length(); ++idx) + { + const char chr = str[idx]; + if (chr == '\0') + { + newstr.append("\\00"); + } + else if (chr == '"' || chr == '+' || chr == ',' || chr == ';' || + chr == '<' || chr == '=' || chr == '>' || chr == '\\') + { + newstr.push_back('\\'); + newstr.push_back(chr); + } + else if (idx == 0 && (chr == '#' || chr == ' ')) + { + newstr.push_back('\\'); + newstr.push_back(chr); + } + else if (idx == str.length() - 1 && chr == ' ') + { + newstr.push_back('\\'); + newstr.push_back(chr); + } + else + { + newstr.push_back(chr); + } + } + + return newstr; + } + + Anope::string EscapeSF(const Anope::string &str) const anope_override + { + if (str.empty()) + return str; + + Anope::string newstr; + newstr.str().reserve(str.length()); + for (size_t idx = 0; idx < str.length(); ++idx) + { + const char chr = str[idx]; + switch (chr) + { + case '\0': + newstr.append("\\00"); + break; + case '(': + newstr.append("\\28"); + break; + case ')': + newstr.append("\\29"); + break; + case '*': + newstr.append("\\2A"); + break; + case '\\': + newstr.append("\\5C"); + break; + default: + newstr.push_back(chr); + break; + } + } + return newstr; + } + private: void BuildReply(int res, LDAPRequest *req) { From e23ea8f8ea6c3bc5f1169d066ad78b82e0db06a9 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Tue, 26 May 2026 09:30:15 +0100 Subject: [PATCH 2/4] Escape user-provided values in ldap_authentication and ldap_oper. --- modules/extra/m_ldap_authentication.cpp | 6 ++++-- modules/extra/m_ldap_oper.cpp | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/extra/m_ldap_authentication.cpp b/modules/extra/m_ldap_authentication.cpp index f89b2413d..f9143eb96 100644 --- a/modules/extra/m_ldap_authentication.cpp +++ b/modules/extra/m_ldap_authentication.cpp @@ -84,7 +84,9 @@ class IdentifyInterface : public LDAPInterface { if (ii->admin_bind) { - Anope::string sf = search_filter.replace_all_cs("%account", ii->req->GetAccount()).replace_all_cs("%object_class", object_class); + Anope::string sf = search_filter + .replace_all_cs("%account", ii->lprov->EscapeSF(ii->req->GetAccount())) + .replace_all_cs("%object_class", object_class); try { Log(LOG_DEBUG) << "m_ldap_authentication: searching for " << sf; @@ -296,7 +298,7 @@ class ModuleLDAPAuthentication : public Module attributes[3].name = this->password_attribute; attributes[3].values.push_back(pass); - Anope::string new_dn = username_attribute + "=" + na->nick + "," + basedn; + Anope::string new_dn = username_attribute + "=" + this->ldap->EscapeDN(na->nick) + "," + basedn; this->ldap->Add(&this->orinterface, new_dn, attributes); } diff --git a/modules/extra/m_ldap_oper.cpp b/modules/extra/m_ldap_oper.cpp index c1c5c6591..06978b0d6 100644 --- a/modules/extra/m_ldap_oper.cpp +++ b/modules/extra/m_ldap_oper.cpp @@ -115,8 +115,12 @@ class LDAPOper : public Module throw LDAPException("Could not search LDAP for opertype settings, invalid configuration."); if (!this->binddn.empty()) - this->ldap->Bind(NULL, this->binddn.replace_all_cs("%a", u->Account()->display), this->password.c_str()); - this->ldap->Search(new IdentifyInterface(this, u), this->basedn, this->filter.replace_all_cs("%a", u->Account()->display)); + { + Anope::string bdn = this->binddn.replace_all_cs("%a", this->ldap->EscapeDN(u->Account()->display)); + this->ldap->Bind(NULL, bdn, this->password.c_str()); + } + Anope::string af = this->filter.replace_all_cs("%a", this->ldap->EscapeSF(u->Account()->display)); + this->ldap->Search(new IdentifyInterface(this, u), this->basedn, af); } catch (const LDAPException &ex) { From 2327c6ac9abf59386962f5786e70135305de9dd9 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Tue, 26 May 2026 10:36:50 +0100 Subject: [PATCH 3/4] Fix an escaped value that wasn't escaped enough in chanstats. --- modules/extra/stats/m_chanstats.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/extra/stats/m_chanstats.cpp b/modules/extra/stats/m_chanstats.cpp index 4d910fffb..c21009d54 100644 --- a/modules/extra/stats/m_chanstats.cpp +++ b/modules/extra/stats/m_chanstats.cpp @@ -343,14 +343,16 @@ class MChanstats : public Module "(nick_, '', 'total'), (nick_, '', 'monthly')," "(nick_, '', 'weekly'), (nick_, '', 'daily');" "END IF;" + "SET @echan = chan_;" + "SET @enick = nick_;" "SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, '," "letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', " "smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', " "smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', " "modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' " - "WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');" + "WHERE (nick='''' OR nick=?) AND (chan='''' OR chan=?)');" "PREPARE update_query FROM @update_query;" - "EXECUTE update_query;" + "EXECUTE update_query using @enick, @echan;" "DEALLOCATE PREPARE update_query;" "END"; this->RunQuery(query); From 8e691eac800937f5ce9a2d773f845b008a312abc Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Tue, 26 May 2026 10:44:44 +0100 Subject: [PATCH 4/4] Update the change log. --- docs/Changes | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Changes b/docs/Changes index d17409d83..51ad25728 100644 --- a/docs/Changes +++ b/docs/Changes @@ -1,6 +1,10 @@ Anope Version 2.0.19-git ------------------------ -No significant changes. +Fixed an escaped value that wasn't escaped enough in chanstats. +Fixed not having a handler for the FNAME message on InspIRCd. +Fixed os_stats reporting the SGLine expiry time as the SQLine expiry time. +Fixed the DNs and search filters not being escaped correctly in the ldap_authentication and ldap_oper modules. +Modernised the advice in docs/LANGUAGE regarding the installation of language packs. Anope Version 2.0.19 --------------------