Re: Bug Report: proxy_auth -i case-insensitive matching broken in Squid 6.x

Andre Bolinhas <[email protected]> Fri, 6 Mar 2026 19:19:56 +0000
Newsgroups gmane.comp.web.squid.general
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--===============8457513259243793488==
Content-Type: multipart/alternative;
 boundary="------------CEgG0PGhEe58ereiUyNKJ28D"
Content-Language: pt_PT

This is a multi-part message in MIME format.
--------------CEgG0PGhEe58ereiUyNKJ28D
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

This is the patch file, tested successfully on squid 6.x and 7.x

--- src/acl/UserData.h.orig     2026-03-06 16:23:44.415748741 +0000
+++ src/acl/UserData.h  2026-03-06 16:23:44.431748826 +0000
@@ -34,11 +34,15 @@
      /* ACLData API */
      const Acl::Options &lineOptions() override;

-    typedef std::set<SBuf,bool(*)(const SBuf&, const SBuf&)> 
UserDataNames_t;
-    UserDataNames_t userDataNames;
+    typedef std::set<SBuf, bool(*)(const SBuf&, const SBuf&)> 
UserDataNames_t;
+
+    /// names added while -i (case-insensitive) was off
+    UserDataNames_t caseSensitiveNames;
+
+    /// names added while -i (case-insensitive) was on; stored lowercased
+    UserDataNames_t caseInsensitiveNames;

      struct {
-        bool case_insensitive;
          bool required;
      } flags;

--- src/acl/UserData.cc.orig    2026-03-06 16:23:44.435748847 +0000
+++ src/acl/UserData.cc 2026-03-06 16:23:44.459748972 +0000
@@ -23,7 +23,7 @@
  bool
  ACLUserData::match(char const *user)
  {
-    debugs(28, 7, "user is " << user << ", case_insensitive is " << 
flags.case_insensitive);
+    debugs(28, 7, "user is " << user);

      if (user == nullptr || strcmp(user, "-") == 0)
          return 0;
@@ -33,9 +33,26 @@
          return 1;
      }

-    bool result = (userDataNames.find(SBuf(user)) != userDataNames.end());
-    debugs(28, 7, "returning " << result);
-    return result;
+    const SBuf userKey(user);
+
+    // check case-sensitive set first (exact match)
+    if (caseSensitiveNames.find(userKey) != caseSensitiveNames.end()) {
+        debugs(28, 7, "returning 1 (case-sensitive match)");
+        return 1;
+    }
+
+    // check case-insensitive set (lowercased lookup)
+    if (!caseInsensitiveNames.empty()) {
+        SBuf lowerUser(userKey);
+        lowerUser.toLower();
+        if (caseInsensitiveNames.find(lowerUser) != 
caseInsensitiveNames.end()) {
+            debugs(28, 7, "returning 1 (case-insensitive match)");
+            return 1;
+        }
+    }
+
+    debugs(28, 7, "returning 0");
+    return 0;
  }

  SBufList
@@ -48,14 +65,18 @@
          return sl;
      }

-    if (flags.case_insensitive)
-        sl.push_back(SBuf("-i"));
+    // dump case-sensitive names first (no flag needed)
+    sl.insert(sl.end(), caseSensitiveNames.begin(), 
caseSensitiveNames.end());

-    sl.insert(sl.end(), userDataNames.begin(), userDataNames.end());
+    // dump case-insensitive names with -i prefix
+    if (!caseInsensitiveNames.empty()) {
+        sl.push_back(SBuf("-i"));
+        sl.insert(sl.end(), caseInsensitiveNames.begin(), 
caseInsensitiveNames.end());
+    }

-    debugs(28,5, "ACLUserData dump output: " <<
-           JoinContainerToSBuf(userDataNames.begin(), userDataNames.end(),
-                               SBuf(" ")));
+    debugs(28, 5, "ACLUserData dump output: " <<
+           caseSensitiveNames.size() << " case-sensitive, " <<
+           caseInsensitiveNames.size() << " case-insensitive users");
      return sl;
  }

@@ -72,9 +93,9 @@
  }

  ACLUserData::ACLUserData() :
-    userDataNames(CaseSensitiveSBufCompare)
+    caseSensitiveNames(CaseSensitiveSBufCompare),
+    caseInsensitiveNames(CaseInsensitveSBufCompare)
  {
-    flags.case_insensitive = false;
      flags.required = false;
  }

@@ -91,63 +112,61 @@
  ACLUserData::parse()
  {
      debugs(28, 2, "parsing user list");
-    flags.case_insensitive = bool(CaseInsensitive_);
+
+    bool caseInsensitive = bool(CaseInsensitive_);

      char *t = nullptr;
-    if ((t = ConfigParser::strtokFile())) {
+    while ((t = ConfigParser::strtokFile())) {
          SBuf s(t);
-        debugs(28, 5, "first token is " << s);
-
-        if (s.cmp("-i",2) == 0) {
-            debugs(28, 5, "Going case-insensitive");
-            flags.case_insensitive = true;
-            // due to how the std::set API work, if we want to change
-            // the comparison function we have to create a new std::set
-            UserDataNames_t newUdn(CaseInsensitveSBufCompare);
-            newUdn.insert(userDataNames.begin(), userDataNames.end());
-            swap(userDataNames,newUdn);
-        } else if (s.cmp("REQUIRED") == 0) {
-            debugs(28, 5, "REQUIRED-type enabled");
-            flags.required = true;
-        } else {
-            if (flags.case_insensitive)
-                s.toLower();
+        debugs(28, 6, "Got token: " << s);

-            debugs(28, 6, "Adding user " << s);
-            userDataNames.insert(s);
+        if (s.cmp("-i", 2) == 0) {
+            debugs(28, DBG_IMPORTANT, "WARNING: ACL uses '-i' as a 
token in the user list; " <<
+                   "use 'acl ... proxy_auth -i ...' line option instead");
+            continue;
          }
-    }
-
-    debugs(28, 3, "Case-insensitive-switch is " << flags.case_insensitive);
-    /* we might inherit from a previous declaration */

-    debugs(28, 4, "parsing following tokens");
+        if (s.cmp("+i", 2) == 0) {
+            debugs(28, DBG_IMPORTANT, "WARNING: ACL uses '+i' as a 
token in the user list; " <<
+                   "use 'acl ... proxy_auth +i ...' line option instead");
+            continue;
+        }

-    while ((t = ConfigParser::strtokFile())) {
-        SBuf s(t);
-        debugs(28, 6, "Got token: " << s);
+        if (s.cmp("REQUIRED") == 0) {
+            debugs(28, 5, "REQUIRED-type enabled");
+            flags.required = true;
+            continue;
+        }

-        if (flags.case_insensitive)
+        if (caseInsensitive) {
              s.toLower();
-
-        debugs(28, 6, "Adding user " << s);
-        userDataNames.insert(s);
+            debugs(28, 6, "Adding user (case-insensitive) " << s);
+            caseInsensitiveNames.insert(s);
+        } else {
+            debugs(28, 6, "Adding user (case-sensitive) " << s);
+            caseSensitiveNames.insert(s);
+        }
      }

-    if (flags.required && !userDataNames.empty()) {
+    if (flags.required && (!caseSensitiveNames.empty() || 
!caseInsensitiveNames.empty())) {
          debugs(28, DBG_PARSE_NOTE(1), "WARNING: detected attempt to 
add usernames to an acl of type REQUIRED");
-        userDataNames.clear();
+        caseSensitiveNames.clear();
+        caseInsensitiveNames.clear();
      }

-    debugs(28,4, "ACL contains " << userDataNames.size() << " users");
+    debugs(28, 4, "ACL contains " << caseSensitiveNames.size() <<
+           " case-sensitive and " << caseInsensitiveNames.size() <<
+           " case-insensitive users");
  }

  bool
  ACLUserData::empty() const
  {
-    debugs(28,6,"required: " << flags.required << ", number of users: " 
<< userDataNames.size());
+    debugs(28, 6, "required: " << flags.required <<
+           ", case-sensitive users: " << caseSensitiveNames.size() <<
+           ", case-insensitive users: " << caseInsensitiveNames.size());
      if (flags.required)
          return false;
-    return userDataNames.empty();
+    return caseSensitiveNames.empty() && caseInsensitiveNames.empty();
  }

On 2026-03-06 6:43 p.m., Alex Rousskov wrote:
> On 2026-03-06 12:32, Andre Bolinhas wrote:
>
>> I can't create the pull request, returns the message "Pull request 
>> creation failed. Validation failed: must be a collaborator"
>
> Googling suggests that you might be trying to modify the official git 
> repository directly. Instead, fork the official git repository, make 
> your changes in your forked repository, and then submit a pull request 
> to merge your changes into the official repository. This process is 
> typical for open source projects.
>
> The following wiki page has related git hints:
> https://wiki.squid-cache.org/DeveloperResources/GitHints
>
> HTH,
>
> Alex.
>
>
>> On 2026-03-05 2:40 p.m., Alex Rousskov wrote:
>>> On 2026-03-04 17:44, Andre Bolinhas wrote:
>>>
>>>> The |proxy_auth -i| ACL (case-insensitive user matching) is broken 
>>>> in Squid 6.x.
>>>
>>> Yes, there are several bugs/problems there. See a long comment above 
>>> Acl::Option class declaration for how things are supposed to work.
>>>
>>> If you can volunteer to work on a fix, please post a pull request as 
>>> discussed at https://wiki.squid-cache.org/MergeProcedure#pull-request
>>>
>>> In that pull request, instead of Option A and Option B, please do this:
>>>
>>> 1. Split ACLUserData::userDataNames into two sets: 
>>> caseSensitiveNames and caseInsensitiveNames. Add tokens to the right 
>>> set, depending on the current CaseInsensitive_ value. Search/print 
>>> both sets as needed. Remove flags.case_insensitive.
>>>
>>> 2. Ignore any '-i' and '+i' tokens in ACLUserData::parse(), with a 
>>> level-1 warning, instead of adding them to a set as if they were 
>>> user names.
>>>
>>> 3. Check other ACLs that use lineOptions() for similar bugs.
>>>
>>>
>>> Thank you,
>>>
>>> Alex.
>>> P.S. I am sorry that our Bugzilla is still down, preventing you from 
>>> using it to report this bug. We can continue to discuss this on GitHub.
>>>
>
--------------CEgG0PGhEe58ereiUyNKJ28D
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html data-lt-installed="true">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body style="padding-bottom: 1px;">
    <p>This is the patch file, tested successfully on squid 6.x and 7.x</p>
    <p>--- src/acl/UserData.h.orig     2026-03-06 16:23:44.415748741
      +0000<br>
      +++ src/acl/UserData.h  2026-03-06 16:23:44.431748826 +0000<br>
      @@ -34,11 +34,15 @@<br>
           /* ACLData API */<br>
           const Acl::Options &amp;lineOptions() override;<br>
      <br>
      -    typedef std::set&lt;SBuf,bool(*)(const SBuf&amp;, const
      SBuf&amp;)&gt; UserDataNames_t;<br>
      -    UserDataNames_t userDataNames;<br>
      +    typedef std::set&lt;SBuf, bool(*)(const SBuf&amp;, const
      SBuf&amp;)&gt; UserDataNames_t;<br>
      +<br>
      +    /// names added while -i (case-insensitive) was off<br>
      +    UserDataNames_t caseSensitiveNames;<br>
      +<br>
      +    /// names added while -i (case-insensitive) was on; stored
      lowercased<br>
      +    UserDataNames_t caseInsensitiveNames;<br>
      <br>
           struct {<br>
      -        bool case_insensitive;<br>
               bool required;<br>
           } flags;<br>
      <br>
      --- src/acl/UserData.cc.orig    2026-03-06 16:23:44.435748847
      +0000<br>
      +++ src/acl/UserData.cc 2026-03-06 16:23:44.459748972 +0000<br>
      @@ -23,7 +23,7 @@<br>
       bool<br>
       ACLUserData::match(char const *user)<br>
       {<br>
      -    debugs(28, 7, "user is " &lt;&lt; user &lt;&lt; ",
      case_insensitive is " &lt;&lt; flags.case_insensitive);<br>
      +    debugs(28, 7, "user is " &lt;&lt; user);<br>
      <br>
           if (user == nullptr || strcmp(user, "-") == 0)<br>
               return 0;<br>
      @@ -33,9 +33,26 @@<br>
               return 1;<br>
           }<br>
      <br>
      -    bool result = (userDataNames.find(SBuf(user)) !=
      userDataNames.end());<br>
      -    debugs(28, 7, "returning " &lt;&lt; result);<br>
      -    return result;<br>
      +    const SBuf userKey(user);<br>
      +<br>
      +    // check case-sensitive set first (exact match)<br>
      +    if (caseSensitiveNames.find(userKey) !=
      caseSensitiveNames.end()) {<br>
      +        debugs(28, 7, "returning 1 (case-sensitive match)");<br>
      +        return 1;<br>
      +    }<br>
      +<br>
      +    // check case-insensitive set (lowercased lookup)<br>
      +    if (!caseInsensitiveNames.empty()) {<br>
      +        SBuf lowerUser(userKey);<br>
      +        lowerUser.toLower();<br>
      +        if (caseInsensitiveNames.find(lowerUser) !=
      caseInsensitiveNames.end()) {<br>
      +            debugs(28, 7, "returning 1 (case-insensitive
      match)");<br>
      +            return 1;<br>
      +        }<br>
      +    }<br>
      +<br>
      +    debugs(28, 7, "returning 0");<br>
      +    return 0;<br>
       }<br>
      <br>
       SBufList<br>
      @@ -48,14 +65,18 @@<br>
               return sl;<br>
           }<br>
      <br>
      -    if (flags.case_insensitive)<br>
      -        sl.push_back(SBuf("-i"));<br>
      +    // dump case-sensitive names first (no flag needed)<br>
      +    sl.insert(sl.end(), caseSensitiveNames.begin(),
      caseSensitiveNames.end());<br>
      <br>
      -    sl.insert(sl.end(), userDataNames.begin(),
      userDataNames.end());<br>
      +    // dump case-insensitive names with -i prefix<br>
      +    if (!caseInsensitiveNames.empty()) {<br>
      +        sl.push_back(SBuf("-i"));<br>
      +        sl.insert(sl.end(), caseInsensitiveNames.begin(),
      caseInsensitiveNames.end());<br>
      +    }<br>
      <br>
      -    debugs(28,5, "ACLUserData dump output: " &lt;&lt;<br>
      -           JoinContainerToSBuf(userDataNames.begin(),
      userDataNames.end(),<br>
      -                               SBuf(" ")));<br>
      +    debugs(28, 5, "ACLUserData dump output: " &lt;&lt;<br>
      +           caseSensitiveNames.size() &lt;&lt; " case-sensitive, "
      &lt;&lt;<br>
      +           caseInsensitiveNames.size() &lt;&lt; "
      case-insensitive users");<br>
           return sl;<br>
       }<br>
      <br>
      @@ -72,9 +93,9 @@<br>
       }<br>
      <br>
       ACLUserData::ACLUserData() :<br>
      -    userDataNames(CaseSensitiveSBufCompare)<br>
      +    caseSensitiveNames(CaseSensitiveSBufCompare),<br>
      +    caseInsensitiveNames(CaseInsensitveSBufCompare)<br>
       {<br>
      -    flags.case_insensitive = false;<br>
           flags.required = false;<br>
       }<br>
      <br>
      @@ -91,63 +112,61 @@<br>
       ACLUserData::parse()<br>
       {<br>
           debugs(28, 2, "parsing user list");<br>
      -    flags.case_insensitive = bool(CaseInsensitive_);<br>
      +<br>
      +    bool caseInsensitive = bool(CaseInsensitive_);<br>
      <br>
           char *t = nullptr;<br>
      -    if ((t = ConfigParser::strtokFile())) {<br>
      +    while ((t = ConfigParser::strtokFile())) {<br>
               SBuf s(t);<br>
      -        debugs(28, 5, "first token is " &lt;&lt; s);<br>
      -<br>
      -        if (s.cmp("-i",2) == 0) {<br>
      -            debugs(28, 5, "Going case-insensitive");<br>
      -            flags.case_insensitive = true;<br>
      -            // due to how the std::set API work, if we want to
      change<br>
      -            // the comparison function we have to create a new
      std::set<br>
      -            UserDataNames_t newUdn(CaseInsensitveSBufCompare);<br>
      -            newUdn.insert(userDataNames.begin(),
      userDataNames.end());<br>
      -            swap(userDataNames,newUdn);<br>
      -        } else if (s.cmp("REQUIRED") == 0) {<br>
      -            debugs(28, 5, "REQUIRED-type enabled");<br>
      -            flags.required = true;<br>
      -        } else {<br>
      -            if (flags.case_insensitive)<br>
      -                s.toLower();<br>
      +        debugs(28, 6, "Got token: " &lt;&lt; s);<br>
      <br>
      -            debugs(28, 6, "Adding user " &lt;&lt; s);<br>
      -            userDataNames.insert(s);<br>
      +        if (s.cmp("-i", 2) == 0) {<br>
      +            debugs(28, DBG_IMPORTANT, "WARNING: ACL uses '-i' as
      a token in the user list; " &lt;&lt;<br>
      +                   "use 'acl ... proxy_auth -i ...' line option
      instead");<br>
      +            continue;<br>
               }<br>
      -    }<br>
      -<br>
      -    debugs(28, 3, "Case-insensitive-switch is " &lt;&lt;
      flags.case_insensitive);<br>
      -    /* we might inherit from a previous declaration */<br>
      <br>
      -    debugs(28, 4, "parsing following tokens");<br>
      +        if (s.cmp("+i", 2) == 0) {<br>
      +            debugs(28, DBG_IMPORTANT, "WARNING: ACL uses '+i' as
      a token in the user list; " &lt;&lt;<br>
      +                   "use 'acl ... proxy_auth +i ...' line option
      instead");<br>
      +            continue;<br>
      +        }<br>
      <br>
      -    while ((t = ConfigParser::strtokFile())) {<br>
      -        SBuf s(t);<br>
      -        debugs(28, 6, "Got token: " &lt;&lt; s);<br>
      +        if (s.cmp("REQUIRED") == 0) {<br>
      +            debugs(28, 5, "REQUIRED-type enabled");<br>
      +            flags.required = true;<br>
      +            continue;<br>
      +        }<br>
      <br>
      -        if (flags.case_insensitive)<br>
      +        if (caseInsensitive) {<br>
                   s.toLower();<br>
      -<br>
      -        debugs(28, 6, "Adding user " &lt;&lt; s);<br>
      -        userDataNames.insert(s);<br>
      +            debugs(28, 6, "Adding user (case-insensitive) "
      &lt;&lt; s);<br>
      +            caseInsensitiveNames.insert(s);<br>
      +        } else {<br>
      +            debugs(28, 6, "Adding user (case-sensitive) "
      &lt;&lt; s);<br>
      +            caseSensitiveNames.insert(s);<br>
      +        }<br>
           }<br>
      <br>
      -    if (flags.required &amp;&amp; !userDataNames.empty()) {<br>
      +    if (flags.required &amp;&amp; (!caseSensitiveNames.empty() ||
      !caseInsensitiveNames.empty())) {<br>
               debugs(28, DBG_PARSE_NOTE(1), "WARNING: detected attempt
      to add usernames to an acl of type REQUIRED");<br>
      -        userDataNames.clear();<br>
      +        caseSensitiveNames.clear();<br>
      +        caseInsensitiveNames.clear();<br>
           }<br>
      <br>
      -    debugs(28,4, "ACL contains " &lt;&lt; userDataNames.size()
      &lt;&lt; " users");<br>
      +    debugs(28, 4, "ACL contains " &lt;&lt;
      caseSensitiveNames.size() &lt;&lt;<br>
      +           " case-sensitive and " &lt;&lt;
      caseInsensitiveNames.size() &lt;&lt;<br>
      +           " case-insensitive users");<br>
       }<br>
      <br>
       bool<br>
       ACLUserData::empty() const<br>
       {<br>
      -    debugs(28,6,"required: " &lt;&lt; flags.required &lt;&lt; ",
      number of users: " &lt;&lt; userDataNames.size());<br>
      +    debugs(28, 6, "required: " &lt;&lt; flags.required &lt;&lt;<br>
      +           ", case-sensitive users: " &lt;&lt;
      caseSensitiveNames.size() &lt;&lt;<br>
      +           ", case-insensitive users: " &lt;&lt;
      caseInsensitiveNames.size());<br>
           if (flags.required)<br>
               return false;<br>
      -    return userDataNames.empty();<br>
      +    return caseSensitiveNames.empty() &amp;&amp;
      caseInsensitiveNames.empty();<br>
       }<br>
      <br>
    </p>
    <div class="moz-cite-prefix">On 2026-03-06 6:43 p.m., Alex Rousskov
      wrote:<br>
    </div>
    <blockquote type="cite"
cite="mid:[email protected]">On
      2026-03-06 12:32, Andre Bolinhas wrote:
      <br>
      <br>
      <blockquote type="cite">I can't create the pull request, returns
        the message "Pull request creation failed. Validation failed:
        must be a collaborator"
        <br>
      </blockquote>
      <br>
      Googling suggests that you might be trying to modify the official
      git repository directly. Instead, fork the official git
      repository, make your changes in your forked repository, and then
      submit a pull request to merge your changes into the official
      repository. This process is typical for open source projects.
      <br>
      <br>
      The following wiki page has related git hints:
      <br>
      <a class="moz-txt-link-freetext" href="https://wiki.squid-cache.org/DeveloperResources/GitHints">https://wiki.squid-cache.org/DeveloperResources/GitHints</a>
      <br>
      <br>
      HTH,
      <br>
      <br>
      Alex.
      <br>
      <br>
      <br>
      <blockquote type="cite">On 2026-03-05 2:40 p.m., Alex Rousskov
        wrote:
        <br>
        <blockquote type="cite">On 2026-03-04 17:44, Andre Bolinhas
          wrote:
          <br>
          <br>
          <blockquote type="cite">The |proxy_auth -i| ACL
            (case-insensitive user matching) is broken in Squid 6.x.
            <br>
          </blockquote>
          <br>
          Yes, there are several bugs/problems there. See a long comment
          above Acl::Option class declaration for how things are
          supposed to work.
          <br>
          <br>
          If you can volunteer to work on a fix, please post a pull
          request as discussed at
          <a class="moz-txt-link-freetext" href="https://wiki.squid-cache.org/MergeProcedure#pull-request">https://wiki.squid-cache.org/MergeProcedure#pull-request</a>
          <br>
          <br>
          In that pull request, instead of Option A and Option B, please
          do this:
          <br>
          <br>
          1. Split ACLUserData::userDataNames into two sets:
          caseSensitiveNames and caseInsensitiveNames. Add tokens to the
          right set, depending on the current CaseInsensitive_ value.
          Search/print both sets as needed. Remove
          flags.case_insensitive.
          <br>
          <br>
          2. Ignore any '-i' and '+i' tokens in ACLUserData::parse(),
          with a level-1 warning, instead of adding them to a set as if
          they were user names.
          <br>
          <br>
          3. Check other ACLs that use lineOptions() for similar bugs.
          <br>
          <br>
          <br>
          Thank you,
          <br>
          <br>
          Alex.
          <br>
          P.S. I am sorry that our Bugzilla is still down, preventing
          you from using it to report this bug. We can continue to
          discuss this on GitHub.
          <br>
          <br>
        </blockquote>
      </blockquote>
      <br>
    </blockquote>
  </body>
  <lt-container></lt-container>
</html>

--------------CEgG0PGhEe58ereiUyNKJ28D--

--===============8457513259243793488==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
squid-users mailing list
[email protected]
https://lists.squid-cache.org/listinfo/squid-users

--===============8457513259243793488==--