3 * $Id: auth_ntlm.cc,v 1.14 2001/10/17 17:09:08 hno Exp $
5 * DEBUG: section 29 NTLM Authenticator
6 * AUTHOR: Robert Collins
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
36 /* The functions in this file handle authentication.
37 * They DO NOT perform access control or auditing.
38 * See acl.c for access control and client_side.c for auditing */
42 #include "auth_ntlm.h"
44 extern AUTHSSETUP authSchemeSetup_ntlm
;
47 authenticateStateFree(authenticateStateData
* r
)
53 static HLPSCB authenticateNTLMHandleReply
;
54 static HLPSCB authenticateNTLMHandleplaceholder
;
55 static AUTHSACTIVE authenticateNTLMActive
;
56 static AUTHSAUTHED authNTLMAuthenticated
;
57 static AUTHSAUTHUSER authenticateNTLMAuthenticateUser
;
58 static AUTHSCONFIGURED authNTLMConfigured
;
59 static AUTHSFIXERR authenticateNTLMFixErrorHeader
;
60 static AUTHSFREE authenticateNTLMFreeUser
;
61 static AUTHSDIRECTION authenticateNTLMDirection
;
62 static AUTHSDECODE authenticateDecodeNTLMAuth
;
63 static AUTHSDUMP authNTLMCfgDump
;
64 static AUTHSFREECONFIG authNTLMFreeConfig
;
65 static AUTHSINIT authNTLMInit
;
66 static AUTHSONCLOSEC authenticateNTLMOnCloseConnection
;
67 static AUTHSCONNLASTHEADER NTLMLastHeader
;
68 static AUTHSUSERNAME authenticateNTLMUsername
;
69 static AUTHSREQFREE authNTLMAURequestFree
;
70 static AUTHSPARSE authNTLMParse
;
71 static AUTHSSTART authenticateNTLMStart
;
72 static AUTHSSTATS authenticateNTLMStats
;
73 static AUTHSSHUTDOWN authNTLMDone
;
75 /* helper callbacks to handle per server state data */
76 static HLPSAVAIL authenticateNTLMHelperServerAvailable
;
77 static HLPSONEQ authenticateNTLMHelperServerOnEmpty
;
79 static statefulhelper
*ntlmauthenticators
= NULL
;
81 CBDATA_TYPE(authenticateStateData
);
83 static int authntlm_initialised
= 0;
85 static MemPool
*ntlm_helper_state_pool
= NULL
;
86 static MemPool
*ntlm_user_pool
= NULL
;
87 static MemPool
*ntlm_request_pool
= NULL
;
88 static auth_ntlm_config
*ntlmConfig
= NULL
;
90 static hash_table
*proxy_auth_cache
= NULL
;
101 debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n");
102 if (ntlmauthenticators
)
103 helperStatefulShutdown(ntlmauthenticators
);
104 authntlm_initialised
= 0;
107 if (ntlmauthenticators
)
108 helperStatefulFree(ntlmauthenticators
);
109 ntlmauthenticators
= NULL
;
110 if (ntlm_helper_state_pool
) {
111 assert(memPoolInUseCount(ntlm_helper_state_pool
) == 0);
112 memPoolDestroy(ntlm_helper_state_pool
);
113 ntlm_helper_state_pool
= NULL
;
115 if (ntlm_request_pool
) {
116 assert(memPoolInUseCount(ntlm_request_pool
) == 0);
117 memPoolDestroy(ntlm_request_pool
);
118 ntlm_request_pool
= NULL
;
120 if (ntlm_user_pool
) {
121 assert(memPoolInUseCount(ntlm_user_pool
) == 0);
122 memPoolDestroy(ntlm_user_pool
);
123 ntlm_user_pool
= NULL
;
125 debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n");
128 /* free any allocated configuration details */
130 authNTLMFreeConfig(authScheme
* scheme
)
132 if (ntlmConfig
== NULL
)
134 assert(ntlmConfig
== scheme
->scheme_data
);
135 if (ntlmConfig
->authenticate
)
136 wordlistDestroy(&ntlmConfig
->authenticate
);
142 authNTLMCfgDump(StoreEntry
* entry
, const char *name
, authScheme
* scheme
)
144 auth_ntlm_config
*config
= scheme
->scheme_data
;
145 wordlist
*list
= config
->authenticate
;
146 storeAppendPrintf(entry
, "%s %s", name
, "ntlm");
147 while (list
!= NULL
) {
148 storeAppendPrintf(entry
, " %s", list
->key
);
151 storeAppendPrintf(entry
, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n",
152 name
, "ntlm", config
->authenticateChildren
,
153 name
, "ntlm", config
->challengeuses
,
154 name
, "ntlm", config
->challengelifetime
);
159 authNTLMParse(authScheme
* scheme
, int n_configured
, char *param_str
)
161 if (scheme
->scheme_data
== NULL
) {
162 assert(ntlmConfig
== NULL
);
163 /* this is the first param to be found */
164 scheme
->scheme_data
= xmalloc(sizeof(auth_ntlm_config
));
165 memset(scheme
->scheme_data
, 0, sizeof(auth_ntlm_config
));
166 ntlmConfig
= scheme
->scheme_data
;
167 ntlmConfig
->authenticateChildren
= 5;
168 ntlmConfig
->challengeuses
= 0;
169 ntlmConfig
->challengelifetime
= 60;
171 ntlmConfig
= scheme
->scheme_data
;
172 if (strcasecmp(param_str
, "program") == 0) {
173 if (ntlmConfig
->authenticate
)
174 wordlistDestroy(&ntlmConfig
->authenticate
);
175 parse_wordlist(&ntlmConfig
->authenticate
);
176 requirePathnameExists("authparam ntlm program", ntlmConfig
->authenticate
->key
);
177 } else if (strcasecmp(param_str
, "children") == 0) {
178 parse_int(&ntlmConfig
->authenticateChildren
);
179 } else if (strcasecmp(param_str
, "max_challenge_reuses") == 0) {
180 parse_int(&ntlmConfig
->challengeuses
);
181 } else if (strcasecmp(param_str
, "max_challenge_lifetime") == 0) {
182 parse_time_t(&ntlmConfig
->challengelifetime
);
184 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str
);
186 /* disable client side request pipelining. There is a race with NTLM when the client
187 * sends a second request on an NTLM connection before the authenticate challenge is
189 * With this patch, the client may fail to authenticate, but squid's state will be
191 * Caveats: this should be a post-parse test, but that can wait for the modular
192 * parser to be integrated.
194 if (ntlmConfig
->authenticate
)
195 Config
.onoff
.pipeline_prefetch
= 0;
200 authSchemeSetup_ntlm(authscheme_entry_t
* authscheme
)
202 assert(!authntlm_initialised
);
203 authscheme
->Active
= authenticateNTLMActive
;
204 authscheme
->configured
= authNTLMConfigured
;
205 authscheme
->parse
= authNTLMParse
;
206 authscheme
->dump
= authNTLMCfgDump
;
207 authscheme
->requestFree
= authNTLMAURequestFree
;
208 authscheme
->freeconfig
= authNTLMFreeConfig
;
209 authscheme
->init
= authNTLMInit
;
210 authscheme
->authAuthenticate
= authenticateNTLMAuthenticateUser
;
211 authscheme
->authenticated
= authNTLMAuthenticated
;
212 authscheme
->authFixHeader
= authenticateNTLMFixErrorHeader
;
213 authscheme
->FreeUser
= authenticateNTLMFreeUser
;
214 authscheme
->authStart
= authenticateNTLMStart
;
215 authscheme
->authStats
= authenticateNTLMStats
;
216 authscheme
->authUserUsername
= authenticateNTLMUsername
;
217 authscheme
->getdirection
= authenticateNTLMDirection
;
218 authscheme
->decodeauth
= authenticateDecodeNTLMAuth
;
219 authscheme
->donefunc
= authNTLMDone
;
220 authscheme
->oncloseconnection
= authenticateNTLMOnCloseConnection
;
221 authscheme
->authConnLastHeader
= NTLMLastHeader
;
224 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
227 authNTLMInit(authScheme
* scheme
)
229 static int ntlminit
= 0;
230 if (ntlmConfig
->authenticate
) {
231 if (!ntlm_helper_state_pool
)
232 ntlm_helper_state_pool
= memPoolCreate("NTLM Helper State data", sizeof(ntlm_helper_state_t
));
234 ntlm_user_pool
= memPoolCreate("NTLM Scheme User Data", sizeof(ntlm_user_t
));
235 if (!ntlm_request_pool
)
236 ntlm_request_pool
= memPoolCreate("NTLM Scheme Request Data", sizeof(ntlm_request_t
));
237 authntlm_initialised
= 1;
238 if (ntlmauthenticators
== NULL
)
239 ntlmauthenticators
= helperStatefulCreate("ntlmauthenticator");
240 if (!proxy_auth_cache
)
241 proxy_auth_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
242 assert(proxy_auth_cache
);
243 ntlmauthenticators
->cmdline
= ntlmConfig
->authenticate
;
244 ntlmauthenticators
->n_to_start
= ntlmConfig
->authenticateChildren
;
245 ntlmauthenticators
->ipc_type
= IPC_TCP_SOCKET
;
246 ntlmauthenticators
->datapool
= ntlm_helper_state_pool
;
247 ntlmauthenticators
->IsAvailable
= authenticateNTLMHelperServerAvailable
;
248 ntlmauthenticators
->OnEmptyQueue
= authenticateNTLMHelperServerOnEmpty
;
249 helperStatefulOpenServers(ntlmauthenticators
);
250 /* TODO: In here send the initial YR to preinitialise the challenge cache */
251 /* Think about this... currently we ask when the challenge is needed. Better? */
253 cachemgrRegister("ntlmauthenticator", "User NTLM Authenticator Stats", authenticateNTLMStats
, 0, 1);
256 CBDATA_INIT_TYPE(authenticateStateData
);
261 authenticateNTLMActive()
263 return (authntlm_initialised
== 1) ? 1 : 0;
270 if ((ntlmConfig
!= NULL
) && (ntlmConfig
->authenticate
!= NULL
) && (ntlmConfig
->authenticateChildren
!= 0) && (ntlmConfig
->challengeuses
> -1) && (ntlmConfig
->challengelifetime
> -1)) {
271 debug(29, 9) ("authNTLMConfigured: returning configured\n");
274 debug(29, 9) ("authNTLMConfigured: returning unconfigured\n");
281 authenticateNTLMDirection(auth_user_request_t
* auth_user_request
)
283 ntlm_request_t
*ntlm_request
= auth_user_request
->scheme_data
;
284 /* null auth_user is checked for by authenticateDirection */
285 switch (ntlm_request
->auth_state
) {
286 case AUTHENTICATE_STATE_NONE
: /* no progress at all. */
287 if (ntlm_request
->flags
.credentials_ok
!= 2)
288 debug(29, 1) ("authenticateNTLMDirection: called before NTLM Authenticate!. Report a bug to squid-dev. au %x\n", auth_user_request
);
290 case AUTHENTICATE_STATE_NEGOTIATE
: /* send to helper */
291 case AUTHENTICATE_STATE_RESPONSE
: /*send to helper */
293 case AUTHENTICATE_STATE_CHALLENGE
: /* send to client */
295 case AUTHENTICATE_STATE_DONE
: /* do nothing.. */
302 * Send the authenticate error header(s). Note: IE has a bug and the NTLM header
303 * must be first. To ensure that, the configure use --enable-auth=ntlm, anything
307 authenticateNTLMFixErrorHeader(auth_user_request_t
* auth_user_request
, HttpReply
* rep
, http_hdr_type type
, request_t
* request
)
309 ntlm_request_t
*ntlm_request
;
310 if (ntlmConfig
->authenticate
) {
311 /* New request, no user details */
312 if (auth_user_request
== NULL
) {
313 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type
);
314 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
315 /* drop the connection */
316 httpHeaderDelByName(&rep
->header
, "keep-alive");
317 /* NTLM has problems if the initial connection is not dropped
318 * I haven't checked the RFC compliance of this hack - RBCollins */
319 request
->flags
.proxy_keepalive
= 0;
321 ntlm_request
= auth_user_request
->scheme_data
;
322 switch (ntlm_request
->auth_state
) {
323 case AUTHENTICATE_STATE_NONE
:
324 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type
);
325 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
326 /* drop the connection */
327 httpHeaderDelByName(&rep
->header
, "keep-alive");
328 /* NTLM has problems if the initial connection is not dropped
329 * I haven't checked the RFC compliance of this hack - RBCollins */
330 request
->flags
.proxy_keepalive
= 0;
332 case AUTHENTICATE_STATE_CHALLENGE
:
333 /* we are 'waiting' for a response */
334 /* pass the challenge to the client */
335 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type
, ntlm_request
->authchallenge
);
336 httpHeaderPutStrf(&rep
->header
, type
, "NTLM %s", ntlm_request
->authchallenge
);
339 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request
->auth_state
);
340 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
347 authNTLMRequestFree(ntlm_request_t
* ntlm_request
)
351 if (ntlm_request
->ntlmnegotiate
)
352 xfree(ntlm_request
->ntlmnegotiate
);
353 if (ntlm_request
->authchallenge
)
354 xfree(ntlm_request
->authchallenge
);
355 if (ntlm_request
->ntlmauthenticate
)
356 xfree(ntlm_request
->ntlmauthenticate
);
357 if (ntlm_request
->authserver
!= NULL
&& ntlm_request
->authserver_deferred
) {
358 debug(29, 9) ("authenticateNTLMRequestFree: releasing server '%d'\n", ntlm_request
->authserver
);
359 helperStatefulReleaseServer(ntlm_request
->authserver
);
360 ntlm_request
->authserver
= NULL
;
362 memPoolFree(ntlm_request_pool
, ntlm_request
);
366 authNTLMAURequestFree(auth_user_request_t
* auth_user_request
)
368 if (auth_user_request
->scheme_data
)
369 authNTLMRequestFree((ntlm_request_t
*) auth_user_request
->scheme_data
);
370 auth_user_request
->scheme_data
= NULL
;
374 authenticateNTLMFreeUser(auth_user_t
* auth_user
)
376 dlink_node
*link
, *tmplink
;
377 ntlm_user_t
*ntlm_user
= auth_user
->scheme_data
;
378 auth_user_hash_pointer
*proxy_auth_hash
;
380 debug(29, 5) ("authenticateNTLMFreeUser: Clearing NTLM scheme data\n");
381 if (ntlm_user
->username
)
382 xfree(ntlm_user
->username
);
383 /* were they linked in by one or more proxy-authenticate headers */
384 link
= ntlm_user
->proxy_auth_list
.head
;
386 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%d'\n", link
->data
);
387 proxy_auth_hash
= link
->data
;
390 dlinkDelete(tmplink
, &ntlm_user
->proxy_auth_list
);
391 hash_remove_link(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
392 /* free the key (usually the proxy_auth header) */
393 xfree(proxy_auth_hash
->key
);
394 memFree(proxy_auth_hash
, MEM_AUTH_USER_HASH
);
396 memPoolFree(ntlm_user_pool
, ntlm_user
);
397 auth_user
->scheme_data
= NULL
;
400 static stateful_helper_callback_t
401 authenticateNTLMHandleplaceholder(void *data
, void *lastserver
, char *reply
)
403 authenticateStateData
*r
= data
;
404 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
406 /* we should only be called for placeholder requests - which have no reply string */
407 assert(reply
== NULL
);
408 assert(r
->auth_user_request
);
409 /* standard callback stuff */
410 valid
= cbdataValid(r
->data
);
412 debug(29, 1) ("AuthenticateNTLMHandlePlacheholder: invalid callback data.\n");
415 /* call authenticateNTLMStart to retry this request */
416 debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n");
417 authenticateNTLMStart(r
->auth_user_request
, r
->handler
, r
->data
);
418 cbdataUnlock(r
->data
);
419 authenticateStateFree(r
);
423 static stateful_helper_callback_t
424 authenticateNTLMHandleReply(void *data
, void *lastserver
, char *reply
)
426 authenticateStateData
*r
= data
;
427 ntlm_helper_state_t
*helperstate
;
429 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
431 auth_user_request_t
*auth_user_request
;
432 auth_user_t
*auth_user
;
433 ntlm_user_t
*ntlm_user
;
434 ntlm_request_t
*ntlm_request
;
435 debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%d' {%s}\n", lastserver
, reply
? reply
: "<NULL>");
436 valid
= cbdataValid(r
->data
);
438 debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%d'.\n", lastserver
);
439 cbdataUnlock(r
->data
);
440 authenticateStateFree(r
);
441 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", S_HELPER_RELEASE
);
442 return S_HELPER_RELEASE
;
445 fatal("authenticateNTLMHandleReply: called with no result string\n");
447 /* seperate out the useful data */
448 if (strncasecmp(reply
, "TT ", 3) == 0) {
450 /* we have been given a Challenge */
451 /* we should check we weren't given an empty challenge */
452 /* copy the challenge to the state data */
453 helperstate
= helperStatefulServerGetData(lastserver
);
454 if (helperstate
== NULL
)
455 fatal("lost NTLM helper state! quitting\n");
456 helperstate
->challenge
= xstrndup(reply
, NTLM_CHALLENGE_SZ
+ 5);
457 helperstate
->challengeuses
= 0;
458 helperstate
->renewed
= squid_curtime
;
459 /* and we satisfy the request that happended on the refresh boundary */
460 /* note this code is now in two places FIXME */
461 assert(r
->auth_user_request
!= NULL
);
462 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
463 auth_user_request
= r
->auth_user_request
;
464 ntlm_request
= auth_user_request
->scheme_data
;
465 assert(ntlm_request
!= NULL
);
466 result
= S_HELPER_DEFER
;
467 /* reserve the server for future authentication */
468 ntlm_request
->authserver_deferred
= 1;
469 debug(29, 9) ("authenticateNTLMHandleReply: helper '%d'\n", lastserver
);
470 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
);
471 ntlm_request
->authserver
= lastserver
;
472 ntlm_request
->authchallenge
= xstrndup(reply
, NTLM_CHALLENGE_SZ
+ 5);
473 } else if (strncasecmp(reply
, "AF ", 3) == 0) {
474 /* we're finished, release the helper */
476 assert(r
->auth_user_request
!= NULL
);
477 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
478 auth_user_request
= r
->auth_user_request
;
479 assert(auth_user_request
->scheme_data
!= NULL
);
480 ntlm_request
= auth_user_request
->scheme_data
;
481 auth_user
= auth_user_request
->auth_user
;
482 ntlm_user
= auth_user_request
->auth_user
->scheme_data
;
483 assert(ntlm_user
!= NULL
);
484 result
= S_HELPER_RELEASE
;
485 /* we only expect OK when finishing the handshake */
486 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_RESPONSE
);
487 ntlm_user
->username
= xstrndup(reply
, MAX_LOGIN_SZ
);
488 ntlm_request
->authserver
= NULL
;
489 ntlm_request
->flags
.credentials_ok
= 1; /* login ok */
490 #ifdef NTLM_FAIL_OPEN
491 } else if (strncasecmp(reply
, "LD ", 3) == 0) {
492 /* This is a variant of BH, which rather than deny access
493 * allows the user through. The helper is starved and then refreshed
494 * via YR, all pending authentications are likely to fail also.
495 * It is meant for those helpers which occasionally fail for
496 * no reason at all (casus belli, NTLMSSP helper on NT domain,
497 * failing about 1 auth out of 1k.
498 * The code is a merge from the BH case with snippets of the AF
500 /* AF code: mark user as authenticated */
502 assert(r
->auth_user_request
!= NULL
);
503 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
504 auth_user_request
= r
->auth_user_request
;
505 assert(auth_user_request
->scheme_data
!= NULL
);
506 ntlm_request
= auth_user_request
->scheme_data
;
507 auth_user
= auth_user_request
->auth_user
;
508 ntlm_user
= auth_user_request
->auth_user
->scheme_data
;
509 assert(ntlm_user
!= NULL
);
510 result
= S_HELPER_RELEASE
;
511 /* we only expect OK when finishing the handshake */
512 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_RESPONSE
);
513 ntlm_user
->username
= xstrndup(reply
, MAX_LOGIN_SZ
);
514 helperstate
= helperStatefulServerGetData(ntlm_request
->authserver
);
515 ntlm_request
->authserver
= NULL
;
516 ntlm_request
->flags
.credentials_ok
= 1; /* login ok */
517 /* BH code: mark helper as broken */
518 /* Not a valid helper response to a YR request. Assert so the helper
519 * programmer will fix their bugs! */
520 assert(ntlm_request
->auth_state
!= AUTHENTICATE_STATE_NEGOTIATE
);
521 /* mark it for starving */
522 helperstate
->starve
= 1;
524 } else if (strncasecmp(reply
, "NA ", 3) == 0) {
525 /* TODO: only work with auth_user here if it exists */
526 assert(r
->auth_user_request
!= NULL
);
527 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
528 auth_user_request
= r
->auth_user_request
;
529 auth_user
= auth_user_request
->auth_user
;
530 assert(auth_user
!= NULL
);
531 ntlm_user
= auth_user
->scheme_data
;
532 ntlm_request
= auth_user_request
->scheme_data
;
533 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
534 /* todo: action of Negotiate state on error */
535 result
= S_HELPER_RELEASE
; /*some error has occured. no more requests */
536 ntlm_request
->authserver
= NULL
;
537 ntlm_request
->flags
.credentials_ok
= 2; /* Login/Usercode failed */
538 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply
);
539 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
540 if ((t
= strchr(reply
, ' '))) /* strip after a space */
542 } else if (strncasecmp(reply
, "NA", 2) == 0) {
543 /* NTLM Helper protocol violation! */
544 fatal("NTLM Helper returned invalid response \"NA\" - a error message MUST be attached\n");
545 } else if (strncasecmp(reply
, "BH ", 3) == 0) {
546 /* TODO kick off a refresh process. This can occur after a YR or after
547 * a KK. If after a YR release the helper and resubmit the request via
548 * Authenticate NTLM start.
549 * If after a KK deny the user's request w/ 407 and mark the helper as
551 assert(r
->auth_user_request
!= NULL
);
552 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
553 auth_user_request
= r
->auth_user_request
;
554 auth_user
= auth_user_request
->auth_user
;
555 assert(auth_user
!= NULL
);
556 ntlm_user
= auth_user
->scheme_data
;
557 ntlm_request
= auth_user_request
->scheme_data
;
558 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
559 result
= S_HELPER_RELEASE
; /*some error has occured. no more requests for
561 assert(ntlm_request
->authserver
? ntlm_request
->authserver
== lastserver
: 1);
562 helperstate
= helperStatefulServerGetData(ntlm_request
->authserver
);
563 ntlm_request
->authserver
= NULL
;
564 if (ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
) {
565 /* The helper broke on YR. It automatically
567 ntlm_request
->flags
.credentials_ok
= 3; /* cannot process */
568 debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %d. Error returned '%s'\n", lastserver
, reply
);
569 /* mark it for starving */
570 helperstate
->starve
= 1;
571 /* resubmit the request. This helper is currently busy, so we will get
572 * a different one. */
573 authenticateNTLMStart(auth_user_request
, r
->handler
, r
->data
);
574 /* don't call the callback */
575 cbdataUnlock(r
->data
);
576 authenticateStateFree(r
);
577 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result
);
580 /* the helper broke on a KK */
581 /* first the standard KK stuff */
582 ntlm_request
->flags
.credentials_ok
= 2; /* Login/Usercode failed */
583 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply
);
584 if ((t
= strchr(reply
, ' '))) /* strip after a space */
586 /* now we mark the helper for resetting. */
587 helperstate
->starve
= 1;
589 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
591 /* TODO: only work with auth_user here if it exists */
592 assert(r
->auth_user_request
!= NULL
);
593 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
594 auth_user_request
= r
->auth_user_request
;
595 auth_user
= auth_user_request
->auth_user
;
596 assert(auth_user
!= NULL
);
597 ntlm_user
= auth_user
->scheme_data
;
598 ntlm_request
= auth_user_request
->scheme_data
;
599 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
600 debug(29, 1) ("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply
);
601 /* restart the authentication process */
602 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
603 ntlm_request
->flags
.credentials_ok
= 3; /* cannot process */
604 assert(ntlm_request
->authserver
? ntlm_request
->authserver
== lastserver
: 1);
605 ntlm_request
->authserver
= NULL
;
607 r
->handler(r
->data
, NULL
);
608 cbdataUnlock(r
->data
);
609 authenticateStateFree(r
);
610 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result
);
615 authenticateNTLMStats(StoreEntry
* sentry
)
617 storeAppendPrintf(sentry
, "NTLM Authenticator Statistics:\n");
618 helperStatefulStats(sentry
, ntlmauthenticators
);
621 /* is a particular challenge still valid ? */
623 authenticateNTLMValidChallenge(ntlm_helper_state_t
* helperstate
)
625 debug(29, 9) ("authenticateNTLMValidChallenge: Challenge is %s\n", helperstate
->challenge
? "Valid" : "Invalid");
626 if (helperstate
->challenge
== NULL
)
631 /* does our policy call for changing the challenge now? */
633 authenticateNTLMChangeChallenge_p(ntlm_helper_state_t
* helperstate
)
635 /* don't check for invalid challenges just for expiry choices */
636 /* this is needed because we have to starve the helper until all old
637 * requests have been satisfied */
638 if (!helperstate
->renewed
) {
639 /* first use, no challenge has been set. Without this check, it will
641 debug(29, 5) ("authenticateNTLMChangeChallenge_p: first use\n");
644 if (helperstate
->challengeuses
> ntlmConfig
->challengeuses
) {
645 debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge uses (%d) exceeded max uses (%d)\n", helperstate
->challengeuses
, ntlmConfig
->challengeuses
);
648 if (helperstate
->renewed
+ ntlmConfig
->challengelifetime
< squid_curtime
) {
649 debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge exceeded max lifetime by %d seconds\n", squid_curtime
- (helperstate
->renewed
+ ntlmConfig
->challengelifetime
));
652 debug(29, 9) ("Challenge is to be reused\n");
656 /* send the initial data to a stateful ntlm authenticator module */
658 authenticateNTLMStart(auth_user_request_t
* auth_user_request
, RH
* handler
, void *data
)
660 authenticateStateData
*r
= NULL
;
661 helper_stateful_server
*server
;
662 ntlm_helper_state_t
*helperstate
;
664 char *sent_string
= NULL
;
665 ntlm_user_t
*ntlm_user
;
666 ntlm_request_t
*ntlm_request
;
667 auth_user_t
*auth_user
;
669 assert(auth_user_request
);
670 auth_user
= auth_user_request
->auth_user
;
671 ntlm_user
= auth_user
->scheme_data
;
672 ntlm_request
= auth_user_request
->scheme_data
;
674 assert(ntlm_request
);
677 assert(auth_user
->auth_type
= AUTH_NTLM
);
678 debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", ntlm_request
->auth_state
);
679 switch (ntlm_request
->auth_state
) {
680 case AUTHENTICATE_STATE_NEGOTIATE
:
681 sent_string
= xstrdup(ntlm_request
->ntlmnegotiate
);
683 case AUTHENTICATE_STATE_RESPONSE
:
684 sent_string
= xstrdup(ntlm_request
->ntlmauthenticate
);
685 assert(ntlm_request
->authserver
);
686 debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%d'.\n", ntlm_request
->authserver
);
689 fatal("Invalid authenticate state for NTLMStart");
692 while (!xisspace(*sent_string
)) /*trim NTLM */
695 while (xisspace(*sent_string
)) /*trim leading spaces */
698 debug(29, 9) ("authenticateNTLMStart: state '%d'\n", ntlm_request
->auth_state
);
699 debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string
);
700 if (ntlmConfig
->authenticate
== NULL
) {
701 debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string
);
705 /* this is ugly TODO: move the challenge generation routines to their own function and
706 * tidy the logic up to make use of the efficiency we now have */
707 switch (ntlm_request
->auth_state
) {
708 case AUTHENTICATE_STATE_NEGOTIATE
:
710 * 1: get a helper server
711 * 2: does it have a challenge?
712 * 3: tell it to get a challenge, or give ntlmauthdone the challenge
714 server
= helperStatefulDefer(ntlmauthenticators
);
715 helperstate
= server
? helperStatefulServerGetData(server
) : NULL
;
716 while ((server
!= NULL
) && authenticateNTLMChangeChallenge_p(helperstate
)) {
717 /* flag this helper for challenge changing */
718 helperstate
->starve
= 1;
719 /* and release the deferred request */
720 helperStatefulReleaseServer(server
);
721 /* Get another deferrable server */
722 server
= helperStatefulDefer(ntlmauthenticators
);
723 helperstate
= server
? helperStatefulServerGetData(server
) : NULL
;
726 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
728 ntlm_request
->authserver
= server
;
729 /* tell the log what helper we have been given */
730 debug(29, 9) ("authenticateNTLMStart: helper '%d' assigned\n", server
);
731 /* server and valid challenge? */
732 if ((server
== NULL
) || !authenticateNTLMValidChallenge(helperstate
)) {
733 /* No server, or server with invalid challenge */
734 r
= cbdataAlloc(authenticateStateData
);
735 r
->handler
= handler
;
738 r
->auth_user_request
= auth_user_request
;
739 if (server
== NULL
) {
740 helperStatefulSubmit(ntlmauthenticators
, NULL
, authenticateNTLMHandleplaceholder
, r
, NULL
);
742 /* Server with invalid challenge */
743 snprintf(buf
, 8192, "YR\n");
744 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, ntlm_request
->authserver
);
747 /* (server != NULL and we have a valid challenge) */
748 /* TODO: turn the below into a function and call from here and handlereply */
749 /* increment the challenge uses */
750 helperstate
->challengeuses
++;
751 /* assign the challenge */
752 ntlm_request
->authchallenge
= xstrndup(helperstate
->challenge
, NTLM_CHALLENGE_SZ
+ 5);
753 /* we're not actually submitting a request, so we need to release the helper
754 * should the connection close unexpectedly
756 ntlm_request
->authserver_deferred
= 1;
761 case AUTHENTICATE_STATE_RESPONSE
:
762 r
= cbdataAlloc(authenticateStateData
);
763 r
->handler
= handler
;
766 r
->auth_user_request
= auth_user_request
;
767 snprintf(buf
, 8192, "KK %s\n", sent_string
);
768 /* getting rid of deferred request status */
769 ntlm_request
->authserver_deferred
= 0;
770 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, ntlm_request
->authserver
);
771 debug(29, 9) ("authenticateNTLMstart: finished\n");
774 fatal("Invalid authenticate state for NTLMStart");
778 /* callback used by stateful helper routines */
780 authenticateNTLMHelperServerAvailable(void *data
)
782 ntlm_helper_state_t
*statedata
= data
;
783 if (statedata
!= NULL
) {
784 if (statedata
->starve
) {
785 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
788 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
792 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
797 authenticateNTLMHelperServerOnEmpty(void *data
)
799 ntlm_helper_state_t
*statedata
= data
;
800 if (statedata
== NULL
)
802 if (statedata
->starve
) {
803 /* we have been starving the helper */
804 debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n");
805 statedata
->starve
= 0;
806 statedata
->challengeuses
= 0;
807 statedata
->renewed
= 0;
808 xfree(statedata
->challenge
);
809 statedata
->challenge
= NULL
;
814 /* clear the NTLM helper of being reserved for future requests */
816 authenticateNTLMReleaseServer(auth_user_request_t
* auth_user_request
)
818 ntlm_request_t
*ntlm_request
;
819 assert(auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
820 assert(auth_user_request
->scheme_data
!= NULL
);
821 ntlm_request
= auth_user_request
->scheme_data
;
822 debug(29, 9) ("authenticateNTLMReleaseServer: releasing server '%d'\n", ntlm_request
->authserver
);
823 helperStatefulReleaseServer(ntlm_request
->authserver
);
824 ntlm_request
->authserver
= NULL
;
827 /* clear any connection related authentication details */
829 authenticateNTLMOnCloseConnection(ConnStateData
* conn
)
831 ntlm_request_t
*ntlm_request
;
832 assert(conn
!= NULL
);
833 if (conn
->auth_user_request
!= NULL
) {
834 assert(conn
->auth_user_request
->scheme_data
!= NULL
);
835 ntlm_request
= conn
->auth_user_request
->scheme_data
;
836 assert(ntlm_request
->conn
== conn
);
837 if (ntlm_request
->authserver
!= NULL
&& ntlm_request
->authserver_deferred
)
838 authenticateNTLMReleaseServer(conn
->auth_user_request
);
839 /* unlock the connection based lock */
840 debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n");
841 /* minor abstraction break here: FIXME */
842 /* Ensure that the auth user request will be getting closed */
843 /* IFF we start persisting the struct after the conn closes - say for logging
844 * then this test may become invalid
846 assert(conn
->auth_user_request
->references
== 1);
847 authenticateAuthUserRequestUnlock(conn
->auth_user_request
);
848 conn
->auth_user_request
= NULL
;
852 /* authenticateUserUsername: return a pointer to the username in the */
854 authenticateNTLMUsername(auth_user_t
* auth_user
)
856 ntlm_user_t
*ntlm_user
= auth_user
->scheme_data
;
858 return ntlm_user
->username
;
862 /* NTLMLastHeader: return a pointer to the last header used in authenticating
863 * the request/conneciton
866 NTLMLastHeader(auth_user_request_t
* auth_user_request
)
868 ntlm_request_t
*ntlm_request
;
869 assert(auth_user_request
!= NULL
);
870 assert(auth_user_request
->scheme_data
!= NULL
);
871 ntlm_request
= auth_user_request
->scheme_data
;
872 return ntlm_request
->ntlmauthenticate
;
876 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
877 * Auth_user structure.
881 authenticateDecodeNTLMAuth(auth_user_request_t
* auth_user_request
, const char *proxy_auth
)
884 assert(auth_user_request
->auth_user
== NULL
);
885 auth_user_request
->auth_user
= authenticateAuthUserNew("ntlm");
886 auth_user_request
->auth_user
->auth_type
= AUTH_NTLM
;
887 auth_user_request
->auth_user
->scheme_data
= memPoolAlloc(ntlm_user_pool
);
888 auth_user_request
->scheme_data
= memPoolAlloc(ntlm_request_pool
);
889 memset(auth_user_request
->scheme_data
, '\0', sizeof(ntlm_request_t
));
890 /* lock for the auth_user_request link */
891 authenticateAuthUserLock(auth_user_request
->auth_user
);
892 node
= dlinkNodeNew();
893 dlinkAdd(auth_user_request
, node
, &auth_user_request
->auth_user
->requests
);
895 /* all we have to do is identify that it's NTLM - the helper does the rest */
896 debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n");
901 authenticateNTLMcmpUsername(ntlm_user_t
* u1
, ntlm_user_t
* u2
)
903 return strcmp(u1
->username
, u2
->username
);
907 /* there is a known race where a single client recieves the same challenge
908 * and sends the same response to squid on a single select cycle.
909 * Check for this and if found ignore the new link
912 authenticateProxyAuthCacheAddLink(const char *key
, auth_user_t
* auth_user
)
914 auth_user_hash_pointer
*proxy_auth_hash
;
916 ntlm_user_t
*ntlm_user
;
917 ntlm_user
= auth_user
->scheme_data
;
918 node
= ntlm_user
->proxy_auth_list
.head
;
919 /* prevent duplicates */
921 if (!strcmp(key
, ((auth_user_hash_pointer
*) node
->data
)->key
))
925 proxy_auth_hash
= memAllocate(MEM_AUTH_USER_HASH
);
926 proxy_auth_hash
->key
= xstrdup(key
);
927 proxy_auth_hash
->auth_user
= auth_user
;
928 dlinkAddTail(proxy_auth_hash
, &proxy_auth_hash
->link
, &ntlm_user
->proxy_auth_list
);
929 hash_join(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
934 authNTLMAuthenticated(auth_user_request_t
* auth_user_request
)
936 ntlm_request_t
*ntlm_request
= auth_user_request
->scheme_data
;
937 if (ntlm_request
->auth_state
== AUTHENTICATE_STATE_DONE
)
939 debug(29, 9) ("User not fully authenticated.\n");
944 authenticateNTLMAuthenticateUser(auth_user_request_t
* auth_user_request
, request_t
* request
, ConnStateData
* conn
, http_hdr_type type
)
946 const char *proxy_auth
;
947 auth_user_hash_pointer
*usernamehash
, *proxy_auth_hash
= NULL
;
948 auth_user_t
*auth_user
;
949 ntlm_request_t
*ntlm_request
;
950 ntlm_user_t
*ntlm_user
;
951 LOCAL_ARRAY(char, ntlmhash
, NTLM_CHALLENGE_SZ
* 2);
953 proxy_auth
= httpHeaderGetStr(&request
->header
, type
);
955 auth_user
= auth_user_request
->auth_user
;
957 assert(auth_user
->auth_type
== AUTH_NTLM
);
958 assert(auth_user
->scheme_data
!= NULL
);
959 assert(auth_user_request
->scheme_data
!= NULL
);
960 ntlm_user
= auth_user
->scheme_data
;
961 ntlm_request
= auth_user_request
->scheme_data
;
962 switch (ntlm_request
->auth_state
) {
963 case AUTHENTICATE_STATE_NONE
:
964 /* we've recieved a negotiate request. pass to a helper */
965 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n", proxy_auth
);
966 if (ntlm_request
->flags
.credentials_ok
== 2) {
967 /* the authentication fialed badly... */
970 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NEGOTIATE
;
971 ntlm_request
->ntlmnegotiate
= xstrndup(proxy_auth
, NTLM_CHALLENGE_SZ
+ 5);
972 conn
->auth_type
= AUTH_NTLM
;
973 conn
->auth_user_request
= auth_user_request
;
974 ntlm_request
->conn
= conn
;
975 /* and lock for the connection duration */
976 debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n");
977 authenticateAuthUserRequestLock(auth_user_request
);
980 case AUTHENTICATE_STATE_NEGOTIATE
:
981 ntlm_request
->auth_state
= AUTHENTICATE_STATE_CHALLENGE
;
982 /* We _MUST_ have the auth challenge by now */
983 assert(ntlm_request
->authchallenge
);
986 case AUTHENTICATE_STATE_CHALLENGE
:
987 /* we should have recieved a NTLM challenge. pass it to the same
989 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth
);
990 /* do a cache lookup here. If it matches it's a successful ntlm
991 * challenge - release the helper and use the existing auth_user
993 if (strncmp("NTLM ", proxy_auth
, 5) == 0) {
994 ntlm_request
->ntlmauthenticate
= xstrdup(proxy_auth
);
996 fatal("Incorrect scheme in auth header\n");
997 /* TODO: more fault tolerance.. reset the auth scheme here */
999 /* cache entries have authenticateauthheaderchallengestring */
1000 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
1001 ntlm_request
->ntlmauthenticate
,
1002 ntlm_request
->authchallenge
);
1003 /* see if we already know this user's authenticate */
1004 debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash
);
1005 assert(proxy_auth_cache
!= NULL
);
1006 proxy_auth_hash
= hash_lookup(proxy_auth_cache
, ntlmhash
);
1007 if (!proxy_auth_hash
) { /* not in the hash table */
1008 debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n");
1009 ntlm_request
->auth_state
= AUTHENTICATE_STATE_RESPONSE
;
1010 /* verify with the ntlm helper */
1012 debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n");
1013 /* throw away the temporary entry */
1014 ntlm_request
->authserver_deferred
= 0;
1015 authenticateNTLMReleaseServer(auth_user_request
);
1016 authenticateAuthUserMerge(auth_user
, proxy_auth_hash
->auth_user
);
1017 auth_user
= proxy_auth_hash
->auth_user
;
1018 auth_user_request
->auth_user
= auth_user
;
1019 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
1021 debug(29, 9) ("found matching cache entry\n");
1022 assert(auth_user
->auth_type
== AUTH_NTLM
);
1023 /* get the existing entries details */
1024 ntlm_user
= auth_user
->scheme_data
;
1025 debug(29, 9) ("Username to be used is %s\n", ntlm_user
->username
);
1026 ntlm_request
->flags
.credentials_ok
= 1; /* authenticated ok */
1027 /* on ntlm auth we do not unlock the auth_user until the
1028 * connection is dropped. Thank MS for this quirk */
1029 auth_user
->expiretime
= current_time
.tv_sec
;
1033 case AUTHENTICATE_STATE_RESPONSE
:
1034 /* auth-challenge pair cache miss. We've just got the response from the helper */
1035 /*add to cache and let them through */
1036 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
1037 /* this connection is authenticated */
1038 debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n",
1039 ntlm_request
->authchallenge
,
1040 ntlm_request
->ntlmauthenticate
,
1041 ntlm_user
->username
);
1042 /* cache entries have authenticateauthheaderchallengestring */
1043 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
1044 ntlm_request
->ntlmauthenticate
,
1045 ntlm_request
->authchallenge
);
1046 /* see if this is an existing user with a different proxy_auth
1048 if ((usernamehash
= hash_lookup(proxy_auth_username_cache
, ntlm_user
->username
))) {
1049 while ((usernamehash
->auth_user
->auth_type
!= auth_user
->auth_type
) && (usernamehash
->next
) && !authenticateNTLMcmpUsername(usernamehash
->auth_user
->scheme_data
, ntlm_user
))
1050 usernamehash
= usernamehash
->next
;
1051 if (usernamehash
->auth_user
->auth_type
== auth_user
->auth_type
) {
1053 * add another link from the new proxy_auth to the
1054 * auth_user structure and update the information */
1055 assert(proxy_auth_hash
== NULL
);
1056 authenticateProxyAuthCacheAddLink(ntlmhash
, usernamehash
->auth_user
);
1057 /* we can't seamlessly recheck the username due to the
1058 * challenge nature of the protocol. Just free the
1059 * temporary auth_user */
1060 authenticateAuthUserMerge(auth_user
, usernamehash
->auth_user
);
1061 auth_user
= usernamehash
->auth_user
;
1062 auth_user_request
->auth_user
= auth_user
;
1065 /* store user in hash's */
1066 authenticateUserNameCacheAdd(auth_user
);
1067 authenticateProxyAuthCacheAddLink(ntlmhash
, auth_user
);
1069 /* set these to now because this is either a new login from an
1070 * existing user or a new user */
1071 auth_user
->expiretime
= current_time
.tv_sec
;
1072 ntlm_request
->flags
.credentials_ok
= 1; /*authenticated ok */
1075 case AUTHENTICATE_STATE_DONE
:
1076 fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n");