<!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;">
    <div class="markdown-here-wrapper">
      <h2>Summary</h2>
      <p>The <code>proxy_auth -i</code> ACL (case-insensitive user
        matching) is broken in Squid 6.x. The <code>-i</code> flag
        causes entries to be lowercased during parse, but the internal
        set container retains a case-sensitive comparator, so lookups
        with mixed-case usernames always fail. <code>proxy_auth_regex
          -i</code> and <code>proxy_auth</code> (without <code>-i</code>)
        work correctly.</p>
      <h2>Squid Version</h2>
      <ul>
        <li><strong>Affected</strong>: Squid 6.14-VCS (likely all Squid
          6.x versions)</li>
        <li><strong>Working</strong>: Squid 5.x (tested, <code>-i</code>
          works correctly)</li>
      </ul>
      <h2>Operating System</h2>
      <ul>
        <li>Debian-based Linux (Artica Proxy appliance)</li>
      </ul>
      <h2>Steps to Reproduce</h2>
      <ol>
        <li>
          <p>Configure a negotiate (SPNEGO/NTLM) authentication helper
            that returns usernames in mixed case (e.g., <code>DOMAIN/Username</code>)</p>
        </li>
        <li>
          <p>Create an ACL user list file <code>/etc/squid3/acls/userlist.txt</code>:</p>
          <pre><code>DOMAIN/username
DOMAIN/Username
</code></pre>
        </li>
        <li>
          <p>Configure <code>proxy_auth -i</code> ACL:</p>
          <pre><code>acl BlockedUsers proxy_auth -i "/etc/squid3/acls/userlist.txt"
http_access deny BlockedUsers
</code></pre>
        </li>
        <li>
          <p>Authenticate via negotiate helper. The helper returns: <code>AF
              oQcwBaADCgEA DOMAIN/Username</code></p>
        </li>
        <li>
          <p><strong>Expected</strong>: <code>proxy_auth -i</code>
            matches <code>DOMAIN/Username</code> against <code>domain/username</code>
            (case-insensitive) → access denied</p>
        </li>
        <li>
          <p><strong>Actual</strong>: <code>proxy_auth -i</code> does
            NOT match → access allowed</p>
        </li>
      </ol>
      <h2>Workarounds</h2>
      <p>Any of these work:</p>
      <ul>
        <li>Use <code>proxy_auth</code> <strong>without</strong> <code>-i</code>,
          ensuring the ACL file contains usernames in the exact case
          returned by the helper</li>
        <li>Use <code>proxy_auth_regex -i</code> instead (regex
          matching handles case-insensitivity correctly)</li>
      </ul>
      <h2>Root Cause Analysis</h2>
      <p>The bug is in <code>src/acl/UserData.cc</code>. In Squid 6.x,
        the <code>-i</code> flag was refactored into an ACL “line
        option” (handled by <code>lineOptions()</code>/<code>Acl::BooleanOptionValue</code>).
        The <code>-i</code> token is consumed by the line options
        parser <strong>before</strong> <code>parse()</code> is called.
        However, the <code>parse()</code> function only recreates the
        internal <code>std::set</code> with a case-insensitive
        comparator when it sees <code>-i</code> as a <strong>token</strong>
        — which never happens in Squid 6.x because the token was already
        consumed.</p>
      <h3>Code trace in <code>UserData.cc</code>:</h3>
      <p><strong>Constructor</strong> — set defaults to case-sensitive:</p>
      <pre><code class="hljs language-cpp">ACLUserData::<span
      class="hljs-built_in">ACLUserData</span>() :
    <span class="hljs-built_in">userDataNames</span>(CaseSensitiveSBufCompare)  <span
      class="hljs-comment">// case-SENSITIVE by default</span>
{ ... }
</code></pre>
      <p><strong>parse()</strong> — the set is never recreated:</p>
      <pre><code class="hljs language-cpp"><span class="hljs-function"><span
      class="hljs-type">void</span> <span class="hljs-title">ACLUserData::parse</span><span
      class="hljs-params">()</span>
</span>{
    <span class="hljs-comment">// Step 1: flag is set correctly from the line option</span>
    flags.case_insensitive = <span class="hljs-built_in">bool</span>(CaseInsensitive_);  <span
      class="hljs-comment">// TRUE (from -i line option)</span>

    <span class="hljs-type">char</span> *t = <span class="hljs-literal">nullptr</span>;
    <span class="hljs-keyword">if</span> ((t = ConfigParser::<span
      class="hljs-built_in">strtokFile</span>())) {
        <span class="hljs-function">SBuf <span class="hljs-title">s</span><span
      class="hljs-params">(t)</span></span>;

        <span class="hljs-comment">// Step 2: this check NEVER matches in Squid 6.x because</span>
        <span class="hljs-comment">//         "-i" was already consumed by the line options parser</span>
        <span class="hljs-keyword">if</span> (s.<span
      class="hljs-built_in">cmp</span>(<span class="hljs-string">"-i"</span>,<span
      class="hljs-number">2</span>) == <span class="hljs-number">0</span>) {
            flags.case_insensitive = <span class="hljs-literal">true</span>;
            <span class="hljs-comment">// THIS BLOCK NEVER EXECUTES — the set is never recreated</span>
            <span class="hljs-function">UserDataNames_t <span
      class="hljs-title">newUdn</span><span class="hljs-params">(CaseInsensitveSBufCompare)</span></span>;
            newUdn.<span class="hljs-built_in">insert</span>(userDataNames.<span
      class="hljs-built_in">begin</span>(), userDataNames.<span
      class="hljs-built_in">end</span>());
            <span class="hljs-built_in">swap</span>(userDataNames, newUdn);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// Step 3: entries ARE correctly lowercased...</span>
            <span class="hljs-keyword">if</span> (flags.case_insensitive)
                s.<span class="hljs-built_in">toLower</span>();
            <span class="hljs-comment">// ...but inserted into a case-SENSITIVE set!</span>
            userDataNames.<span class="hljs-built_in">insert</span>(s);
        }
    }
    <span class="hljs-comment">// ... (remaining entries also lowercased and inserted into case-sensitive set)</span>
}
</code></pre>
      <p><strong>match()</strong> — lookup uses case-sensitive
        comparison on lowercase entries:</p>
      <pre><code class="hljs language-cpp"><span class="hljs-function"><span
      class="hljs-type">bool</span> <span class="hljs-title">ACLUserData::match</span><span
      class="hljs-params">(<span class="hljs-type">char</span> <span
      class="hljs-type">const</span> *user)</span>
</span>{
    <span class="hljs-comment">// user = "DOMAIN/Username" (original case from auth helper)</span>
    <span class="hljs-comment">// userDataNames contains "domain/username" (lowercased during parse)</span>
    <span class="hljs-comment">// userDataNames comparator = CaseSensitiveSBufCompare (never changed!)</span>

    <span class="hljs-type">bool</span> result = (userDataNames.<span
      class="hljs-built_in">find</span>(<span class="hljs-built_in">SBuf</span>(user)) != userDataNames.<span
      class="hljs-built_in">end</span>());
    <span class="hljs-comment">// find("DOMAIN/Username") in set{"domain/username"} with CASE-SENSITIVE compare</span>
    <span class="hljs-comment">// → NOT FOUND (because "DOMAIN/Username" != "domain/username")</span>

    <span class="hljs-keyword">return</span> result;  <span
      class="hljs-comment">// returns false — BUG</span>
}
</code></pre>
      <h3>In Squid 5.x (working correctly):</h3>
      <p>In Squid 5.x, <code>-i</code> was passed through to <code>parse()</code>
        as a regular token. So the <code>if (s.cmp("-i",2) == 0)</code>
        block DID execute, and the set was properly recreated with <code>CaseInsensitveSBufCompare</code>.
        The case-insensitive comparator made <code>find()</code> work
        regardless of case.</p>
      <p>In Squid 6.x, <code>-i</code> was refactored into a generic
        ACL “line option” — it is now consumed by the line options
        parser <strong>before</strong> <code>parse()</code> is called.
        The flag value is correctly propagated to <code>CaseInsensitive_</code>
        (and then to <code>flags.case_insensitive</code>), and entries
        are correctly lowercased during insertion. However, the set
        comparator is never switched from case-sensitive to
        case-insensitive because the code path that does this (<code>if
          (s.cmp("-i",2) == 0)</code>) is never reached.</p>
      <h2>Debug Evidence</h2>
      <h3>Squid 6.14-VCS debug output (<code>debug_options ALL,1 28,9
          29,9 84,9</code>):</h3>
      <p><strong>ACL parsing</strong> — entries correctly lowercased, 2
        unique users:</p>
      <pre><code>UserData.cc(93)  parse: parsing user list
UserData.cc(99)  parse: first token is CHILD01/administrator
UserData.cc(116) parse: Adding user child01/administrator
UserData.cc(121) parse: Case-insensitive-switch is 1
UserData.cc(124) parse: parsing following tokens
UserData.cc(128) parse: Got token: <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
UserData.cc(133) parse: Adding user <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
UserData.cc(128) parse: Got token: <a class="moz-txt-link-abbreviated" href="mailto:Administrator@abolinhas.lab">Administrator@abolinhas.lab</a>
UserData.cc(133) parse: Adding user <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
UserData.cc(128) parse: Got token: CHILD01/Administrator
UserData.cc(133) parse: Adding user child01/administrator
UserData.cc(142) parse: ACL contains 2 users
</code></pre>
      <p><strong>Helper response</strong> — username correctly
        extracted:</p>
      <pre><code>helper.cc(1122)      helperStatefulHandleRead: accumulated[38]=AF oQcwBaADCgEA CHILD01/Administrator
Reply.cc(43)         finalize: Parsing helper buffer
UserRequest.cc(267)  HandleReply: got reply={result=OK, notes={token: oQcwBaADCgEA; user: CHILD01/Administrator; }}
UserRequest.cc(338)  HandleReply: authenticated user CHILD01/Administrator
UserRequest.cc(357)  HandleReply: Successfully validated user via Negotiate. Username 'CHILD01/Administrator'
</code></pre>
      <p><strong>Match with <code>-i</code> — FAILS (BUG):</strong></p>
      <pre><code>UserData.cc(26) match: user is CHILD01/Administrator, case_insensitive is 1
UserData.cc(37) match: returning 0
</code></pre>
      <p><strong>Match without <code>-i</code> — WORKS (proves the
          issue is with <code>-i</code>):</strong></p>
      <pre><code>UserData.cc(26) match: user is CHILD01/Administrator, case_insensitive is 0
UserData.cc(37) match: returning 1
</code></pre>
      <h2>Proof of Concept — Full Debug Session</h2>
      <p>Below is the complete step-by-step debug session performed on a
        live Squid 6.14-VCS instance<br>
        to isolate and confirm the bug.</p>
      <h3>Environment</h3>
      <ul>
        <li><strong>Squid server</strong>: 192.168.60.58, Squid 6.14-VCS
          on Debian-based Linux (Artica Proxy)</li>
        <li><strong>Client machine</strong>: 192.168.60.31, Windows 10
          domain-joined to <code>CHILD01</code> (child domain of <code>ARTICATECH</code>)</li>
        <li><strong>Authentication</strong>: Negotiate (SPNEGO with NTLM
          fallback) via external helper</li>
        <li><strong>Auth helper</strong>: Returns <code>AF <blob>
            CHILD01/Administrator</code> on successful authenticate</li>
      </ul>
      <h3>Step 1 — Baseline configuration (broken)</h3>
      <p>ACL user list file (<code>/etc/squid3/acls/container_963.1.txt</code>):</p>
      <pre><code>CHILD01/administrator
<a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
<a class="moz-txt-link-abbreviated" href="mailto:Administrator@abolinhas.lab">Administrator@abolinhas.lab</a>
CHILD01/Administrator
</code></pre>
      <p>File verified clean with <code>xxd</code> — Unix line endings
        (0x0A), no BOM, no hidden characters:</p>
      <pre><code>00000000: 4348 494c 4430 312f 6164 6d69 6e69 7374  CHILD01/administ
00000010: 7261 746f 720a 6164 6d69 6e69 7374 7261  rator.administra
00000020: 746f 7240 6162 6f6c 696e 6861 732e 6c61  <a class="moz-txt-link-abbreviated" href="mailto:tor@abolinhas.la">tor@abolinhas.la</a>
00000030: 620a 4164 6d69 6e69 7374 7261 746f 7240  b.Administrator@
00000040: 6162 6f6c 696e 6861 732e 6c61 620a 4348  abolinhas.lab.CH
00000050: 494c 4430 312f 4164 6d69 6e69 7374 7261  ILD01/Administra
00000060: 746f 720a                                tor.
</code></pre>
      <p>squid.conf ACL (in <code>/etc/squid3/http_access.conf</code>):</p>
      <pre><code>acl AnnotateRule587 annotate_transaction accessrule=Rule587
acl Group953 proxy_auth  -i "/etc/squid3/acls/container_963.1.txt"
http_access deny Group953 AnnotateRule587 all
</code></pre>
      <h3>Step 2 — Enable debug logging</h3>
      <pre><code class="hljs language-bash"><span class="hljs-comment"># Set debug options in /etc/squid3/logging.conf</span>
debug_options ALL,1 28,9 29,9 84,9
<span class="hljs-comment"># Section 28 = Access Control (ACL matching, UserData)</span>
<span class="hljs-comment"># Section 29 = Authenticator (negotiate helper handling)</span>
<span class="hljs-comment"># Section 84 = Helper I/O (helper stdin/stdout)</span>

squid -k reconfigure
</code></pre>
      <h3>Step 3 — Observe ACL parsing (cache.log)</h3>
      <p>Squid parses the ACL file with <code>-i</code> enabled.
        Entries are lowercased during insertion.<br>
        After case-insensitive deduplication, the ACL contains 2 unique
        users:</p>
      <pre><code>2026/03/04 18:23:57.734 kid1| 28,2| UserData.cc(93)  parse: parsing user list
2026/03/04 18:23:57.734 kid1| 28,5| UserData.cc(99)  parse: first token is CHILD01/administrator
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(116) parse: Adding user child01/administrator
2026/03/04 18:23:57.734 kid1| 28,3| UserData.cc(121) parse: Case-insensitive-switch is 1
2026/03/04 18:23:57.734 kid1| 28,4| UserData.cc(124) parse: parsing following tokens
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(128) parse: Got token: <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(133) parse: Adding user <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(128) parse: Got token: <a class="moz-txt-link-abbreviated" href="mailto:Administrator@abolinhas.lab">Administrator@abolinhas.lab</a>
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(133) parse: Adding user <a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a>
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(128) parse: Got token: CHILD01/Administrator
2026/03/04 18:23:57.734 kid1| 28,6| UserData.cc(133) parse: Adding user child01/administrator
2026/03/04 18:23:57.734 kid1| 28,4| UserData.cc(142) parse: ACL contains 2 users
</code></pre>
      <p>Observation: <code>Case-insensitive-switch is 1</code> (flag
        is set correctly), entries are lowercased<br>
        (<code>child01/administrator</code>, <code><a class="moz-txt-link-abbreviated" href="mailto:administrator@abolinhas.lab">administrator@abolinhas.lab</a></code>).
        Parsing looks correct.</p>
      <h3>Step 4 — Client authenticates via Negotiate (NTLM-in-SPNEGO)</h3>
      <p>Client on 192.168.60.31 sends CONNECT through the proxy. The
        negotiate helper performs<br>
        two-step NTLM-in-SPNEGO authentication:</p>
      <p><strong>Step 4a — NTLM Type 2 challenge (TT response):</strong><br>
        ```<br>
        2026/03/04 18:29:02.696 kid1| 84,5| helper.cc(1112)
        helperStatefulHandleRead: helperStatefulHandleRead: 376 bytes
        from negotiateauthenticator #Hlpr91<br>
        2026/03/04 18:29:02.696 kid1| 84,3| Reply.cc(43) finalize:
        Parsing helper buffer<br>
        2026/03/04 18:29:02.696 kid1| 29,8| UserRequest.cc(267)
        HandleReply: hlpRes693 got reply={result=TT, notes={token:
        TlRMTVNTUA…AAAA=; }}<br>
        2026/03/04 18:29:02.696 kid1| 29,4| UserRequest.cc(313)
        HandleReply: Need to challenge the client with a server token</p>
      <pre><code>
**Step 4b — NTLM Type 3 validation (AF response):**
</code></pre>
      <p>2026/03/04 18:29:02.724 kid1| 84,9| helper.cc(1447)
        helperStatefulDispatch: helperStatefulDispatch busying helper
        negotiateauthenticator #Hlpr91<br>
        2026/03/04 18:29:02.724 kid1| 84,5| helper.cc(1476)
        helperStatefulDispatch: helperStatefulDispatch: Request sent to
        negotiateauthenticator #Hlpr91, 764 bytes<br>
        2026/03/04 18:29:02.731 kid1| 84,5| helper.cc(1112)
        helperStatefulHandleRead: helperStatefulHandleRead: 38 bytes
        from negotiateauthenticator #Hlpr91<br>
        2026/03/04 18:29:02.732 kid1| 84,9| helper.cc(1122)
        helperStatefulHandleRead: accumulated[38]=AF oQcwBaADCgEA
        CHILD01/Administrator<br>
        2026/03/04 18:29:02.732 kid1| 84,3| helper.cc(1134)
        helperStatefulHandleRead: helperStatefulHandleRead: end of reply
        found<br>
        2026/03/04 18:29:02.732 kid1| 84,3| Reply.cc(43) finalize:
        Parsing helper buffer<br>
        2026/03/04 18:29:02.732 kid1| 84,3| Reply.cc(61) finalize: Buff
        length is larger than 2<br>
        2026/03/04 18:29:02.732 kid1| 29,8| UserRequest.cc(267)
        HandleReply: hlpRes693 got reply={result=OK, notes={token:
        oQcwBaADCgEA; user: CHILD01/Administrator; }}<br>
        2026/03/04 18:29:02.732 kid1| 29,4| UserRequest.cc(338)
        HandleReply: authenticated user CHILD01/Administrator<br>
        2026/03/04 18:29:02.732 kid1| 29,4| UserRequest.cc(357)
        HandleReply: Successfully validated user via Negotiate. Username
        ‘CHILD01/Administrator’</p>
      <pre><code>
Observation: The helper returns exactly `AF oQcwBaADCgEA CHILD01/Administrator` (38 bytes
including newline). Squid correctly parses the reply, extracts the token and username. The
authenticated username is set to `CHILD01/Administrator`.

### Step 5 — ACL match with `-i` FAILS (the bug)

When Squid evaluates `http_access deny Group953`, it checks the `proxy_auth -i` ACL:
</code></pre>
      <p>2026/03/04 18:27:58.119 kid1| 28,7| UserData.cc(26) match: user
        is CHILD01/Administrator, case_insensitive is 1<br>
        2026/03/04 18:27:58.119 kid1| 28,7| UserData.cc(37) match:
        returning 0</p>
      <pre><code>
**Result: `returning 0` — NO MATCH.**

The ACL check for Group953 returns 0 (no match), so the deny rule does not trigger.
Access is allowed through the final allow rule:
</code></pre>
      <p>2026/03/04 18:29:02.732 kid1| 28,5| Acl.cc(145) matches:
        checking Group953<br>
        2026/03/04 18:29:02.732 kid1| 28,3| Acl.cc(172) matches:
        checked: Group953 = 0</p>
      <pre><code>
Access log confirms user is NOT denied (note `accessrule: final_allow`):
</code></pre>
      <p>1772648820.870 75169 192.168.60.31 TCP_TUNNEL/200 5123 CONNECT
        sapo.pt:443 CHILD01/Administrator HIER_DIRECT/213.13.145.114:443
        - accessrule:%20final_allow</p>
      <pre><code>
### Step 6 — Remove `-i` flag, ACL match SUCCEEDS

Changed the ACL definition to remove the `-i` flag:
```bash
# Before:
acl Group953 proxy_auth  -i "/etc/squid3/acls/container_963.1.txt"
# After:
acl Group953 proxy_auth "/etc/squid3/acls/container_963.1.txt"

squid -k reconfigure
</code></pre>
      <p>After the next authentication from the same client:</p>
      <pre><code>2026/03/04 18:34:11.740 kid1| 28,7| UserData.cc(26) match: user is CHILD01/Administrator, case_insensitive is 0
2026/03/04 18:34:11.740 kid1| 28,7| UserData.cc(37) match: returning 1
</code></pre>
      <p><strong>Result: <code>returning 1</code> — MATCH!</strong></p>
      <p>Without <code>-i</code>, entries are stored in original case
        from the file. The file contains<br>
        <code>CHILD01/Administrator</code> (line 4) which exactly
        matches the helper’s username. The deny<br>
        rule triggers.</p>
      <p>Access log confirms user IS now denied (note <code>accessrule:
          Rule587</code> and <code>ERR_ACCESS_DENIED</code>):</p>
      <pre><code>1772649291.245     12 192.168.60.31 TCP_DENIED/200 0 CONNECT static.foxnews.com:443 CHILD01/Administrator HIER_NONE/-:- - accessrule:%20Rule587 ERR_ACCESS_DENIED
1772649291.188      2 192.168.60.31 NONE_NONE_ABORTED/403 65843 GET <a class="moz-txt-link-freetext" href="https://static.foxnews.com/.../favicon-96x96.png">https://static.foxnews.com/.../favicon-96x96.png</a> CHILD01/Administrator HIER_NONE/-:- text/html ERR_ACCESS_DENIED
</code></pre>
      <h3>Step 7 — Additional confirmation: <code>proxy_auth_regex -i</code>
        also works</h3>
      <p>Changing the ACL to use <code>proxy_auth_regex -i</code> with
        the same file:</p>
      <pre><code>acl Group953 proxy_auth_regex  -i "/etc/squid3/acls/container_963.1.txt"
</code></pre>
      <p>This also correctly matches and denies the user. <code>proxy_auth_regex</code>
        is unaffected<br>
        because it uses <code>ACLRegexData</code> (regex matching)
        instead of <code>ACLUserData</code> (set-based matching).</p>
      <h3>Step 8 — Downgrade to Squid 5.x confirms fix</h3>
      <p>After downgrading from Squid 6.14-VCS to Squid 5.x on the same
        machine, the original<br>
        configuration with <code>proxy_auth -i</code> works correctly —
        the deny rule matches and blocks<br>
        the user as expected.</p>
      <pre><code class="hljs language-bash">
acl Group953 proxy_auth -i <span class="hljs-string">"/etc/squid3/acls/container_963.1.txt"</span>

squid -k reconfigure
</code></pre>
      <p>After the next authentication from the same client:</p>
      <pre><code>2026/03/04 18:34:11.740 kid1| 28,7| UserData.cc(26) match: user is CHILD01/Administrator, case_insensitive is 1
2026/03/04 18:34:11.740 kid1| 28,7| UserData.cc(37) match: returning 1
</code></pre>
      <p><strong>Result: <code>returning 1</code> — MATCH!</strong></p>
      <p>On Squd5.X With <code>-i</code>, entries are stored in
        original case from the file. The file contains<br>
        <code>CHILD01/Administrator</code> (line 4) which exactly
        matches the helper’s username. The deny<br>
        rule triggers.</p>
      <p>Access log confirms user IS now denied (note <code>accessrule:
          Rule587</code> and <code>ERR_ACCESS_DENIED</code>):</p>
      <pre><code>1772649291.245     12 192.168.60.31 TCP_DENIED/200 0 CONNECT static.foxnews.com:443 CHILD01/Administrator HIER_NONE/-:- - accessrule:%20Rule587 ERR_ACCESS_DENIED
1772649291.188      2 192.168.60.31 NONE_NONE_ABORTED/403 65843 GET <a class="moz-txt-link-freetext" href="https://static.foxnews.com/.../favicon-96x96.png">https://static.foxnews.com/.../favicon-96x96.png</a> CHILD01/Administrator HIER_NONE/-:- text/html ERR_ACCESS_DENIED
</code></pre>
      <h3>PoC Summary Table</h3>
      <table>
        <thead><tr>
            <th>Configuration</th>
            <th align="center">Squid 6.14-VCS</th>
            <th align="center">Squid 5.x</th>
          </tr>
        </thead><tbody>
          <tr>
            <td><code>proxy_auth -i</code> (file)</td>
            <td align="center">BROKEN</td>
            <td align="center">Works</td>
          </tr>
          <tr>
            <td><code>proxy_auth</code> (no <code>-i</code>, file)</td>
            <td align="center">Works</td>
            <td align="center">Works</td>
          </tr>
          <tr>
            <td><code>proxy_auth_regex -i</code> (file)</td>
            <td align="center">Works</td>
            <td align="center">Works</td>
          </tr>
        </tbody>
      </table>
      <h2>Suggested Fix</h2>
      <p>Either of these approaches would fix the bug:</p>
      <h3>Option A: Recreate the set when <code>CaseInsensitive_</code>
        is set (in <code>parse()</code>)</h3>
      <p>Add set recreation at the beginning of <code>parse()</code>
        when the flag comes from the line option:</p>
      <pre><code class="hljs language-cpp"><span class="hljs-function"><span
      class="hljs-type">void</span> <span class="hljs-title">ACLUserData::parse</span><span
      class="hljs-params">()</span>
</span>{
    flags.case_insensitive = <span class="hljs-built_in">bool</span>(CaseInsensitive_);

    <span class="hljs-comment">// If case-insensitive was set via line option, ensure the set</span>
    <span class="hljs-comment">// uses the case-insensitive comparator</span>
    <span class="hljs-keyword">if</span> (flags.case_insensitive) {
        <span class="hljs-function">UserDataNames_t <span
      class="hljs-title">newUdn</span><span class="hljs-params">(CaseInsensitveSBufCompare)</span></span>;
        newUdn.<span class="hljs-built_in">insert</span>(userDataNames.<span
      class="hljs-built_in">begin</span>(), userDataNames.<span
      class="hljs-built_in">end</span>());
        std::<span class="hljs-built_in">swap</span>(userDataNames, newUdn);
    }

    <span class="hljs-comment">// ... rest of parse() unchanged</span>
}
</code></pre>
      <h3>Option B: Lowercase the search key in <code>match()</code></h3>
      <pre><code class="hljs language-cpp"><span class="hljs-function"><span
      class="hljs-type">bool</span> <span class="hljs-title">ACLUserData::match</span><span
      class="hljs-params">(<span class="hljs-type">char</span> <span
      class="hljs-type">const</span> *user)</span>
</span>{
    <span class="hljs-built_in">debugs</span>(<span class="hljs-number">28</span>, <span
      class="hljs-number">7</span>, <span class="hljs-string">"user is "</span> << user << <span
      class="hljs-string">", case_insensitive is "</span> << flags.case_insensitive);

    <span class="hljs-keyword">if</span> (user == <span
      class="hljs-literal">nullptr</span> || <span class="hljs-built_in">strcmp</span>(user, <span
      class="hljs-string">"-"</span>) == <span class="hljs-number">0</span>)
        <span class="hljs-keyword">return</span> <span
      class="hljs-number">0</span>;

    <span class="hljs-keyword">if</span> (flags.required) {
        <span class="hljs-built_in">debugs</span>(<span
      class="hljs-number">28</span>, <span class="hljs-number">7</span>, <span
      class="hljs-string">"aclMatchUser: user REQUIRED and auth-info present."</span>);
        <span class="hljs-keyword">return</span> <span
      class="hljs-number">1</span>;
    }

    <span class="hljs-function">SBuf <span class="hljs-title">key</span><span
      class="hljs-params">(user)</span></span>;
    <span class="hljs-keyword">if</span> (flags.case_insensitive)
        key.<span class="hljs-built_in">toLower</span>();

    <span class="hljs-type">bool</span> result = (userDataNames.<span
      class="hljs-built_in">find</span>(key) != userDataNames.<span
      class="hljs-built_in">end</span>());
    <span class="hljs-built_in">debugs</span>(<span class="hljs-number">28</span>, <span
      class="hljs-number">7</span>, <span class="hljs-string">"returning "</span> << result);
    <span class="hljs-keyword">return</span> result;
}
</code></pre>
      <p>Option A is more correct because it also fixes the set
        comparator for any future operations. Option B is simpler but
        relies on both sides being lowercased rather than using a proper
        case-insensitive comparator.</p>
      <h2>Configuration Context</h2>
      <p>This was observed with a negotiate (SPNEGO/Kerberos with NTLM
        fallback) authentication helper. The helper returns usernames in
        mixed case as received from Active Directory SSPI (e.g., <code>DOMAIN/Username</code>).
        The <code>proxy_auth -i</code> ACL should match these usernames
        case-insensitively against the ACL file entries, but fails to do
        so in Squid 6.x.</p>
      <pre><code>auth_param negotiate program /path/to/negotiate-helper
acl BlockedUsers proxy_auth -i "/path/to/userlist.txt"
http_access deny BlockedUsers
</code></pre>
    </div>
  </body>
</html>