3 * $Id: auth_ntlm.cc,v 1.44 2004/12/20 16:30:40 robertc 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"
43 #include "authenticate.h"
45 #include "client_side.h"
46 #include "HttpReply.h"
47 #include "HttpRequest.h"
48 /* TODO remove this include */
49 #include "ntlmScheme.h"
52 authenticateStateFree(authenticateStateData
* r
)
58 static HLPSCB authenticateNTLMHandleReply
;
59 static HLPSCB authenticateNTLMHandleplaceholder
;
60 static AUTHSSTATS authenticateNTLMStats
;
62 /* helper callbacks to handle per server state data */
63 static HLPSAVAIL authenticateNTLMHelperServerAvailable
;
64 static HLPSONEQ authenticateNTLMHelperServerOnEmpty
;
66 static statefulhelper
*ntlmauthenticators
= NULL
;
68 CBDATA_TYPE(authenticateStateData
);
70 static int authntlm_initialised
= 0;
72 static MemAllocatorProxy
*ntlm_helper_state_pool
= NULL
;
73 static MemAllocatorProxy
*ntlm_user_hash_pool
= NULL
;
75 static auth_ntlm_config ntlmConfig
;
77 static hash_table
*proxy_auth_cache
= NULL
;
85 /* move to ntlmScheme.cc */
89 /* TODO: this should be a Config call. */
90 debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n");
92 if (ntlmauthenticators
)
93 helperStatefulShutdown(ntlmauthenticators
);
95 authntlm_initialised
= 0;
100 if (ntlmauthenticators
)
101 helperStatefulFree(ntlmauthenticators
);
103 ntlmauthenticators
= NULL
;
107 if (ntlm_helper_state_pool
) {
108 delete ntlm_helper_state_pool
;
109 ntlm_helper_state_pool
= NULL
;
112 /* Removed for some reason..
113 if (ntlm_user_pool) {
114 delete ntlm_user_pool;ntlm_user_pool = NULL;
119 debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n");
122 /* free any allocated configuration details */
124 AuthNTLMConfig::done()
127 wordlistDestroy(&authenticate
);
131 AuthNTLMConfig::dump(StoreEntry
* entry
, const char *name
, AuthConfig
* scheme
)
133 wordlist
*list
= authenticate
;
134 storeAppendPrintf(entry
, "%s %s", name
, "ntlm");
136 while (list
!= NULL
) {
137 storeAppendPrintf(entry
, " %s", list
->key
);
141 storeAppendPrintf(entry
, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n",
142 name
, "ntlm", authenticateChildren
,
143 name
, "ntlm", challengeuses
,
144 name
, "ntlm", (int) challengelifetime
);
148 AuthNTLMConfig::AuthNTLMConfig()
150 /* TODO Move into initialisation list */
151 authenticateChildren
= 5;
153 challengelifetime
= 60;
157 AuthNTLMConfig::parse(AuthConfig
* scheme
, int n_configured
, char *param_str
)
159 if (strcasecmp(param_str
, "program") == 0) {
161 wordlistDestroy(&authenticate
);
163 parse_wordlist(&authenticate
);
165 requirePathnameExists("authparam ntlm program", authenticate
->key
);
166 } else if (strcasecmp(param_str
, "children") == 0) {
167 parse_int(&authenticateChildren
);
168 } else if (strcasecmp(param_str
, "max_challenge_reuses") == 0) {
169 parse_int(&challengeuses
);
170 } else if (strcasecmp(param_str
, "max_challenge_lifetime") == 0) {
171 parse_time_t(&challengelifetime
);
173 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str
);
177 * disable client side request pipelining. There is a race with
178 * NTLM when the client sends a second request on an NTLM
179 * connection before the authenticate challenge is sent. With
180 * this patch, the client may fail to authenticate, but squid's
181 * state will be preserved. Caveats: this should be a post-parse
182 * test, but that can wait for the modular parser to be integrated.
185 Config
.onoff
.pipeline_prefetch
= 0;
189 AuthNTLMConfig::type() const
191 return ntlmScheme::GetInstance().type();
194 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
197 AuthNTLMConfig::init(AuthConfig
* scheme
)
199 static int ntlminit
= 0;
202 if (!ntlm_helper_state_pool
)
203 ntlm_helper_state_pool
= new MemAllocatorProxy("NTLM Helper State data", sizeof(ntlm_helper_state_t
));
205 if (!ntlm_user_hash_pool
)
207 ntlm_user_hash_pool
= new MemAllocatorProxy("NTLM Header Hash Data", sizeof(struct ProxyAuthCachePointer
));
209 authntlm_initialised
= 1;
211 if (ntlmauthenticators
== NULL
)
212 ntlmauthenticators
= helperStatefulCreate("ntlmauthenticator");
214 if (!proxy_auth_cache
)
215 proxy_auth_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
217 assert(proxy_auth_cache
);
219 ntlmauthenticators
->cmdline
= authenticate
;
221 ntlmauthenticators
->n_to_start
= authenticateChildren
;
223 ntlmauthenticators
->ipc_type
= IPC_STREAM
;
225 ntlmauthenticators
->datapool
= ntlm_helper_state_pool
;
227 ntlmauthenticators
->IsAvailable
= authenticateNTLMHelperServerAvailable
;
229 ntlmauthenticators
->OnEmptyQueue
= authenticateNTLMHelperServerOnEmpty
;
231 helperStatefulOpenServers(ntlmauthenticators
);
234 * TODO: In here send the initial YR to preinitialise the
238 * Think about this... currently we ask when the challenge
242 cachemgrRegister("ntlmauthenticator",
243 "NTLM User Authenticator Stats",
244 authenticateNTLMStats
, 0, 1);
248 CBDATA_INIT_TYPE(authenticateStateData
);
253 AuthNTLMConfig::active() const
255 return authntlm_initialised
== 1;
259 AuthNTLMConfig::configured() const
261 if ((authenticate
!= NULL
) && (authenticateChildren
!= 0) && (challengeuses
> -1) && (challengelifetime
> -1)) {
262 debug(29, 9) ("authNTLMConfigured: returning configured\n");
266 debug(29, 9) ("authNTLMConfigured: returning unconfigured\n");
272 AuthNTLMUserRequest::module_direction()
274 /* null auth_user is checked for by authenticateDirection */
276 switch (auth_state
) {
278 /* no progress at all. */
280 case AUTHENTICATE_STATE_NONE
:
281 debug(29, 1) ("AuthNTLMUserRequest::direction: called before NTLM Authenticate!. Report a bug to squid-dev.\n");
284 case AUTHENTICATE_STATE_FAILED
:
289 case AUTHENTICATE_STATE_NEGOTIATE
:
293 case AUTHENTICATE_STATE_RESPONSE
:
298 case AUTHENTICATE_STATE_CHALLENGE
:
303 case AUTHENTICATE_STATE_DONE
:
311 * Send the authenticate error header(s). Note: IE has a bug and the NTLM header
312 * must be first. To ensure that, the configure use --enable-auth=ntlm, anything
316 AuthNTLMConfig::fixHeader(auth_user_request_t
*auth_user_request
, HttpReply
*rep
, http_hdr_type type
, HttpRequest
* request
)
318 AuthNTLMUserRequest
*ntlm_request
;
321 /* New request, no user details */
323 if (auth_user_request
== NULL
) {
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 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
333 assert (ntlm_request
);
335 switch (ntlm_request
->auth_state
) {
337 case AUTHENTICATE_STATE_NONE
:
339 case AUTHENTICATE_STATE_FAILED
:
340 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type
);
341 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
342 /* drop the connection */
343 httpHeaderDelByName(&rep
->header
, "keep-alive");
344 /* NTLM has problems if the initial connection is not dropped
345 * I haven't checked the RFC compliance of this hack - RBCollins */
346 request
->flags
.proxy_keepalive
= 0;
349 case AUTHENTICATE_STATE_CHALLENGE
:
350 /* we are 'waiting' for a response */
351 /* pass the challenge to the client */
352 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type
, ntlm_request
->authchallenge
);
353 httpHeaderPutStrf(&rep
->header
, type
, "NTLM %s", ntlm_request
->authchallenge
);
357 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request
->auth_state
);
358 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
364 NTLMUser::~NTLMUser()
366 dlink_node
*link
, *tmplink
;
367 ProxyAuthCachePointer
*proxy_auth_hash
;
368 debug(29, 5) ("NTLMUser::~NTLMUser: Clearing NTLM scheme data\n");
370 /* were they linked in by one or more proxy-authenticate headers */
371 link
= proxy_auth_list
.head
;
374 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%p'\n", link
->data
);
375 proxy_auth_hash
= static_cast<ProxyAuthCachePointer
*>(link
->data
);
378 dlinkDelete(tmplink
, &proxy_auth_list
);
379 hash_remove_link(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
380 /* free the key (usually the proxy_auth header) */
381 xfree(proxy_auth_hash
->key
);
382 ntlm_user_hash_pool
->free(proxy_auth_hash
);
387 static stateful_helper_callback_t
388 authenticateNTLMHandleplaceholder(void *data
, void *lastserver
, char *reply
)
390 authenticateStateData
*r
= static_cast<authenticateStateData
*>(data
);
391 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
392 /* we should only be called for placeholder requests - which have no reply string */
393 assert(reply
== NULL
);
394 assert(r
->auth_user_request
);
395 /* standard callback stuff */
397 if (!cbdataReferenceValid(r
->data
)) {
398 debug(29, 1) ("AuthenticateNTLMHandlePlacheholder: invalid callback data.\n");
402 /* call authenticateNTLMStart to retry this request */
403 debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n");
405 r
->auth_user_request
->start(r
->handler
, r
->data
);
407 cbdataReferenceDone(r
->data
);
409 authenticateStateFree(r
);
414 static stateful_helper_callback_t
415 authenticateNTLMHandleReply(void *data
, void *lastserver
, char *reply
)
417 authenticateStateData
*r
= static_cast<authenticateStateData
*>(data
);
418 ntlm_helper_state_t
*helperstate
;
419 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
421 auth_user_request_t
*auth_user_request
;
422 auth_user_t
*auth_user
;
423 ntlm_user_t
*ntlm_user
;
424 AuthNTLMUserRequest
*ntlm_request
;
425 debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%p' {%s}\n", lastserver
, reply
? reply
: "<NULL>");
427 if (!cbdataReferenceValid(r
->data
)) {
428 debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver
);
429 cbdataReferenceDone(r
->data
);
430 authenticateStateFree(r
);
431 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", S_HELPER_RELEASE
);
432 return S_HELPER_RELEASE
;
437 * TODO: this occurs when a helper crashes. We should clean
438 * up that helpers resources and queued requests.
440 fatal("authenticateNTLMHandleReply: called with no result string\n");
443 /* seperate out the useful data */
444 if (strncasecmp(reply
, "TT ", 3) == 0) {
446 /* we have been given a Challenge */
447 /* we should check we weren't given an empty challenge */
448 /* copy the challenge to the state data */
449 helperstate
= static_cast<ntlm_helper_state_t
*>(helperStatefulServerGetData(static_cast<helper_stateful_server
*>(lastserver
)));
451 if (helperstate
== NULL
)
452 fatal("lost NTLM helper state! quitting\n");
454 helperstate
->challenge
= xstrdup(reply
);
456 helperstate
->challengeuses
= 0;
458 helperstate
->renewed
= squid_curtime
;
460 /* and we satisfy the request that happended on the refresh boundary */
461 /* note this code is now in two places FIXME */
462 assert(r
->auth_user_request
!= NULL
);
464 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
466 auth_user_request
= r
->auth_user_request
;
468 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
470 assert(ntlm_request
!= NULL
);
472 result
= S_HELPER_DEFER
;
474 /* reserve the server for future authentication */
475 ntlm_request
->authserver_deferred
= 1;
477 debug(29, 9) ("authenticateNTLMHandleReply: helper '%p'\n", lastserver
);
479 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
);
481 ntlm_request
->authserver
= static_cast<helper_stateful_server
*>(lastserver
);
483 ntlm_request
->authchallenge
= xstrdup(reply
);
484 } else if (strncasecmp(reply
, "AF ", 3) == 0) {
485 /* we're finished, release the helper */
487 assert(r
->auth_user_request
!= NULL
);
488 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
489 auth_user_request
= r
->auth_user_request
;
490 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
491 assert(ntlm_request
);
492 auth_user
= auth_user_request
->user();
493 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user_request
->user());
494 assert(ntlm_user
!= NULL
);
495 result
= S_HELPER_RELEASE
;
496 /* we only expect OK when finishing the handshake */
497 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_RESPONSE
);
498 ntlm_user
->username(xstrdup(reply
));
499 ntlm_request
->authserver
= NULL
;
500 #ifdef NTLM_FAIL_OPEN
502 } else if (strncasecmp(reply
, "LD ", 3) == 0) {
503 /* This is a variant of BH, which rather than deny access
504 * allows the user through. The helper is starved and then refreshed
505 * via YR, all pending authentications are likely to fail also.
506 * It is meant for those helpers which occasionally fail for
507 * no reason at all (casus belli, NTLMSSP helper on NT domain,
508 * failing about 1 auth out of 1k.
509 * The code is a merge from the BH case with snippets of the AF
511 /* AF code: mark user as authenticated */
513 assert(r
->auth_user_request
!= NULL
);
514 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
515 auth_user_request
= r
->auth_user_request
;
516 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
517 assert(ntlm_request
);
518 auth_user
= auth_user_request
->user();
519 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user_request
->user());
520 assert(ntlm_user
!= NULL
);
521 result
= S_HELPER_RELEASE
;
522 /* we only expect LD when finishing the handshake */
523 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_RESPONSE
);
524 ntlm_user
->username_
= xstrdup(reply
);
525 helperstate
= static_cast<ntlm_helper_state_t
*>(helperStatefulServerGetData(ntlm_request
->authserver
));
526 ntlm_request
->authserver
= NULL
;
527 /* BH code: mark helper as broken */
528 /* mark it for starving */
529 helperstate
->starve
= 1;
532 } else if (strncasecmp(reply
, "NA ", 3) == 0) {
533 /* TODO: only work with auth_user here if it exists */
534 assert(r
->auth_user_request
!= NULL
);
535 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
536 auth_user_request
= r
->auth_user_request
;
537 auth_user
= auth_user_request
->user();
538 assert(auth_user
!= NULL
);
539 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
540 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
541 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
542 /* todo: action of Negotiate state on error */
543 result
= S_HELPER_RELEASE
; /*some error has occured. no more requests */
544 ntlm_request
->authserver
= NULL
;
545 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply
);
546 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FAILED
;
548 if ((t
= strchr(reply
, ' '))) /* strip after a space */
550 } else if (strncasecmp(reply
, "NA", 2) == 0) {
551 /* NTLM Helper protocol violation! */
552 fatal("NTLM Helper returned invalid response \"NA\" - a error message MUST be attached\n");
553 } else if (strncasecmp(reply
, "BH ", 3) == 0) {
554 /* TODO kick off a refresh process. This can occur after a YR or after
555 * a KK. If after a YR release the helper and resubmit the request via
556 * Authenticate NTLM start.
557 * If after a KK deny the user's request w/ 407 and mark the helper as
559 assert(r
->auth_user_request
!= NULL
);
560 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
561 auth_user_request
= r
->auth_user_request
;
562 auth_user
= auth_user_request
->user();
563 assert(auth_user
!= NULL
);
564 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
565 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
566 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
567 /*some error has occured. no more requests for
569 result
= S_HELPER_RELEASE
;
570 assert(ntlm_request
->authserver
? ntlm_request
->authserver
== lastserver
: 1);
571 helperstate
= static_cast<ntlm_helper_state_t
*>(helperStatefulServerGetData(ntlm_request
->authserver
));
572 ntlm_request
->authserver
= NULL
;
574 if (ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
) {
575 /* The helper broke on YR. It automatically
577 debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %p. Error returned '%s'\n", lastserver
, reply
);
578 /* mark it for starving */
579 helperstate
->starve
= 1;
580 /* resubmit the request. This helper is currently busy, so we will get
581 * a different one. Our auth state stays the same */
582 auth_user_request
->start(r
->handler
, r
->data
);
583 /* don't call the callback */
584 cbdataReferenceDone(r
->data
);
585 authenticateStateFree(r
);
586 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result
);
590 /* the helper broke on a KK */
591 /* first the standard KK stuff */
592 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply
);
594 if ((t
= strchr(reply
, ' '))) /* strip after a space */
597 /* now we mark the helper for resetting. */
598 helperstate
->starve
= 1;
600 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FAILED
;
602 /* TODO: only work with auth_user here if it exists */
603 /* TODO: take the request state into consideration */
604 assert(r
->auth_user_request
!= NULL
);
605 assert(r
->auth_user_request
->user()->auth_type
== AUTH_NTLM
);
606 auth_user_request
= r
->auth_user_request
;
607 auth_user
= auth_user_request
->user();
608 assert(auth_user
!= NULL
);
609 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
610 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
611 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
612 debug(29, 1) ("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply
);
613 /* **** NOTE THIS CODE IS EFFECTIVELY UNTESTED **** */
614 /* restart the authentication process */
615 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
616 assert(ntlm_request
->authserver
? ntlm_request
->authserver
== lastserver
: 1);
617 ntlm_request
->authserver
= NULL
;
620 r
->handler(r
->data
, NULL
);
621 cbdataReferenceDone(r
->data
);
622 authenticateStateFree(r
);
623 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result
);
628 authenticateNTLMStats(StoreEntry
* sentry
)
630 storeAppendPrintf(sentry
, "NTLM Authenticator Statistics:\n");
631 helperStatefulStats(sentry
, ntlmauthenticators
);
634 /* is a particular challenge still valid ? */
636 authenticateNTLMValidChallenge(ntlm_helper_state_t
* helperstate
)
638 debug(29, 9) ("authenticateNTLMValidChallenge: Challenge is %s\n", helperstate
->challenge
? "Valid" : "Invalid");
640 if (helperstate
->challenge
== NULL
)
646 /* does our policy call for changing the challenge now? */
648 authenticateNTLMChangeChallenge_p(ntlm_helper_state_t
* helperstate
)
650 /* don't check for invalid challenges just for expiry choices */
651 /* this is needed because we have to starve the helper until all old
652 * requests have been satisfied */
654 if (!helperstate
->renewed
) {
655 /* first use, no challenge has been set. Without this check, it will
657 debug(29, 5) ("authenticateNTLMChangeChallenge_p: first use\n");
661 if (helperstate
->challengeuses
> ntlmConfig
.challengeuses
) {
662 debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge uses (%d) exceeded max uses (%d)\n", helperstate
->challengeuses
, ntlmConfig
.challengeuses
);
666 if (helperstate
->renewed
+ ntlmConfig
.challengelifetime
< squid_curtime
) {
667 debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge exceeded max lifetime by %d seconds\n", (int) (squid_curtime
- (helperstate
->renewed
+ ntlmConfig
.challengelifetime
)));
671 debug(29, 9) ("Challenge is to be reused\n");
675 /* send the initial data to a stateful ntlm authenticator module */
677 AuthNTLMUserRequest::module_start(RH
* handler
, void *data
)
679 authenticateStateData
*r
= NULL
;
680 helper_stateful_server
*server
;
681 ntlm_helper_state_t
*helperstate
;
683 char *sent_string
= NULL
;
684 ntlm_user_t
*ntlm_user
;
685 auth_user_t
*auth_user
;
687 auth_user
= this->user();
688 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
691 assert(auth_user
->auth_type
== AUTH_NTLM
);
692 debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", auth_state
);
694 switch (auth_state
) {
696 case AUTHENTICATE_STATE_NEGOTIATE
:
697 sent_string
= ntlmnegotiate
;
700 case AUTHENTICATE_STATE_RESPONSE
:
701 sent_string
= ntlmauthenticate
;
703 debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%p'.\n", authserver
);
707 fatal("Invalid authenticate state for NTLMStart");
710 while (!xisspace(*sent_string
)) /*trim NTLM */
713 while (xisspace(*sent_string
)) /*trim leading spaces */
716 debug(29, 9) ("authenticateNTLMStart: state '%d'\n", auth_state
);
718 debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string
);
720 if (ntlmConfig
.authenticate
== NULL
) {
721 debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string
);
726 /* this is ugly TODO: move the challenge generation routines to their own function and
727 * tidy the logic up to make use of the efficiency we now have */
728 switch (auth_state
) {
730 case AUTHENTICATE_STATE_NEGOTIATE
:
732 * 1: get a helper server
733 * 2: does it have a challenge?
734 * 3: tell it to get a challenge, or give ntlmauthdone the challenge
736 server
= helperStatefulDefer(ntlmauthenticators
);
737 helperstate
= server
? static_cast<ntlm_helper_state_t
*>(helperStatefulServerGetData(server
)) : NULL
;
739 while ((server
!= NULL
) && authenticateNTLMChangeChallenge_p(helperstate
)) {
740 /* flag this helper for challenge changing */
741 helperstate
->starve
= 1;
742 /* and release the deferred request */
743 helperStatefulReleaseServer(server
);
744 /* Get another deferrable server */
745 server
= helperStatefulDefer(ntlmauthenticators
);
746 helperstate
= server
? static_cast<ntlm_helper_state_t
*>(helperStatefulServerGetData(server
)) : NULL
;
750 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
754 /* tell the log what helper we have been given */
755 debug(29, 9) ("authenticateNTLMStart: helper '%p' assigned\n", server
);
757 /* server and valid challenge? */
758 if ((server
== NULL
) || !authenticateNTLMValidChallenge(helperstate
)) {
759 /* No server, or server with invalid challenge */
760 r
= cbdataAlloc(authenticateStateData
);
761 r
->handler
= handler
;
762 r
->data
= cbdataReference(data
);
763 r
->auth_user_request
= this;
765 if (server
== NULL
) {
766 helperStatefulSubmit(ntlmauthenticators
, NULL
, authenticateNTLMHandleplaceholder
, r
, NULL
);
768 /* Server with invalid challenge */
769 snprintf(buf
, 8192, "YR\n");
770 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, authserver
);
773 /* (server != NULL and we have a valid challenge) */
774 /* TODO: turn the below into a function and call from here and handlereply */
775 /* increment the challenge uses */
776 helperstate
->challengeuses
++;
777 /* assign the challenge */
778 authchallenge
= xstrdup(helperstate
->challenge
);
779 /* we're not actually submitting a request, so we need to release the helper
780 * should the connection close unexpectedly
782 authserver_deferred
= 1;
788 case AUTHENTICATE_STATE_RESPONSE
:
789 r
= cbdataAlloc(authenticateStateData
);
790 r
->handler
= handler
;
791 r
->data
= cbdataReference(data
);
792 r
->auth_user_request
= this;
793 snprintf(buf
, 8192, "KK %s\n", sent_string
);
794 /* getting rid of deferred request status */
795 authserver_deferred
= 0;
796 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, authserver
);
797 debug(29, 9) ("authenticateNTLMstart: finished\n");
801 fatal("Invalid authenticate state for NTLMStart");
805 /* callback used by stateful helper routines */
807 authenticateNTLMHelperServerAvailable(void *data
)
809 ntlm_helper_state_t
*statedata
= static_cast<ntlm_helper_state_t
*>(data
);
811 if (statedata
!= NULL
) {
812 if (statedata
->starve
) {
813 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
816 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
821 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
826 authenticateNTLMHelperServerOnEmpty(void *data
)
828 ntlm_helper_state_t
*statedata
= static_cast<ntlm_helper_state_t
*>(data
);
830 if (statedata
== NULL
)
833 if (statedata
->starve
) {
834 /* we have been starving the helper */
835 debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n");
836 statedata
->starve
= 0;
837 statedata
->challengeuses
= 0;
838 statedata
->renewed
= 0;
839 xfree(statedata
->challenge
);
840 statedata
->challenge
= NULL
;
845 /* clear the NTLM helper of being reserved for future requests */
847 authenticateNTLMReleaseServer(auth_user_request_t
* auth_user_request
)
849 AuthNTLMUserRequest
*ntlm_request
;
850 assert(auth_user_request
->user()->auth_type
== AUTH_NTLM
);
851 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
852 assert (ntlm_request
);
853 debug(29, 9) ("authenticateNTLMReleaseServer: releasing server '%p'\n", ntlm_request
->authserver
);
854 helperStatefulReleaseServer(ntlm_request
->authserver
);
855 ntlm_request
->authserver
= NULL
;
858 /* clear any connection related authentication details */
860 AuthNTLMUserRequest::onConnectionClose(ConnStateData
*conn
)
862 assert(conn
!= NULL
);
864 if (conn
->auth_user_request
!= NULL
) {
865 assert (conn
->auth_user_request
== this);
866 assert(this->conn
== conn
);
868 if (authserver
!= NULL
&& authserver_deferred
)
869 authenticateNTLMReleaseServer(this);
871 /* unlock the connection based lock */
872 debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n");
874 /* This still breaks the abstraction, but is at least read only now.
875 * If needed, this could be ignored, as the conn deletion will also unlock
876 * the auth user request.
880 conn
->auth_user_request
= NULL
;
884 /* NTLMLastHeader: return a pointer to the last header used in authenticating
885 * the request/conneciton
888 AuthNTLMUserRequest::connLastHeader()
890 return ntlmauthenticate
;
894 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
895 * Auth_user structure.
898 AuthNTLMConfig::decode(char const *proxy_auth
)
900 NTLMUser
*newUser
= new NTLMUser(&ntlmConfig
);
901 AuthNTLMUserRequest
*auth_user_request
= new AuthNTLMUserRequest ();
902 assert(auth_user_request
->user() == NULL
);
903 auth_user_request
->user(newUser
);
904 auth_user_request
->user()->auth_type
= AUTH_NTLM
;
905 auth_user_request
->user()->addRequest(auth_user_request
);
907 /* all we have to do is identify that it's NTLM - the helper does the rest */
908 debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n");
909 return auth_user_request
;
913 authenticateNTLMcmpUsername(ntlm_user_t
* u1
, ntlm_user_t
* u2
)
915 return strcmp(u1
->username(), u2
->username());
919 /* there is a known race where a single client recieves the same challenge
920 * and sends the same response to squid on a single select cycle.
921 * Check for this and if found ignore the new link
924 authenticateProxyAuthCacheAddLink(const char *key
, auth_user_t
* auth_user
)
927 struct ProxyAuthCachePointer
*proxy_auth_hash
;
929 ntlm_user_t
*ntlm_user
;
930 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
931 node
= ntlm_user
->proxy_auth_list
.head
;
932 /* prevent duplicates */
936 if (!strcmp(key
, (char const *)((struct ProxyAuthCachePointer
*) node
->data
)->key
))
942 proxy_auth_hash
= static_cast<ProxyAuthCachePointer
*>(ntlm_user_hash_pool
->alloc());
943 proxy_auth_hash
->key
= xstrdup(key
);
944 proxy_auth_hash
->auth_user
= auth_user
;
945 dlinkAddTail(proxy_auth_hash
, &proxy_auth_hash
->link
, &ntlm_user
->proxy_auth_list
);
946 hash_join(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
950 AuthNTLMUserRequest::authenticated() const
952 if (auth_state
== AUTHENTICATE_STATE_DONE
)
955 debug(29, 9) ("User not fully authenticated.\n");
961 AuthNTLMUserRequest::authenticate(HttpRequest
* request
, ConnStateData::Pointer conn
, http_hdr_type type
)
963 const char *proxy_auth
;
965 struct ProxyAuthCachePointer
*proxy_auth_hash
= NULL
;
966 auth_user_hash_pointer
*usernamehash
;
967 /* TODO: rename this!! */
968 auth_user_t
*auth_user
;
969 AuthNTLMUserRequest
*ntlm_request
;
970 ntlm_user_t
*ntlm_user
;
971 LOCAL_ARRAY(char, ntlmhash
, NTLM_CHALLENGE_SZ
* 2);
973 proxy_auth
= httpHeaderGetStr(&request
->header
, type
);
977 assert(auth_user
->auth_type
== AUTH_NTLM
);
978 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
980 assert (ntlm_request
);
981 /* Check that we are in the client side, where we can generate
984 if (conn
.getRaw() == NULL
) {
985 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FAILED
;
986 debug(29, 1) ("authenticateNTLMAuthenticateUser: attempt to perform authentication without a connection!\n");
990 switch (ntlm_request
->auth_state
) {
992 case AUTHENTICATE_STATE_NONE
:
993 /* we've recieved a negotiate request. pass to a helper */
994 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n", proxy_auth
);
995 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NEGOTIATE
;
996 ntlm_request
->ntlmnegotiate
= xstrdup(proxy_auth
);
997 conn
->auth_type
= AUTH_NTLM
;
998 conn
->auth_user_request
= this;
999 ntlm_request
->conn
= conn
;
1000 /* and lock for the connection duration */
1001 debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n");
1010 case AUTHENTICATE_STATE_NEGOTIATE
:
1011 ntlm_request
->auth_state
= AUTHENTICATE_STATE_CHALLENGE
;
1013 /* We _MUST_ have the auth challenge by now */
1014 assert(ntlm_request
->authchallenge
);
1020 case AUTHENTICATE_STATE_CHALLENGE
:
1021 /* we should have recieved a NTLM challenge. pass it to the same
1023 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth
);
1025 /* do a cache lookup here. If it matches it's a successful ntlm
1026 * challenge - release the helper and use the existing auth_user
1029 if (strncmp("NTLM ", proxy_auth
, 5) == 0) {
1030 ntlm_request
->ntlmauthenticate
= xstrdup(proxy_auth
);
1032 fatal("Incorrect scheme in auth header\n");
1033 /* TODO: more fault tolerance.. reset the auth scheme here */
1036 /* cache entries have authenticateauthheaderchallengestring */
1037 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
1038 ntlm_request
->ntlmauthenticate
,
1039 ntlm_request
->authchallenge
);
1041 /* see if we already know this user's authenticate */
1042 debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash
);
1044 assert(proxy_auth_cache
!= NULL
);
1046 proxy_auth_hash
= static_cast<ProxyAuthCachePointer
*>(hash_lookup(proxy_auth_cache
, ntlmhash
));
1048 if (!proxy_auth_hash
) { /* not in the hash table */
1049 debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n");
1050 ntlm_request
->auth_state
= AUTHENTICATE_STATE_RESPONSE
;
1051 /* verify with the ntlm helper */
1053 debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n");
1054 /* throw away the temporary entry */
1055 ntlm_request
->authserver_deferred
= 0;
1056 authenticateNTLMReleaseServer(this);
1057 authenticateAuthUserMerge(auth_user
, proxy_auth_hash
->auth_user
);
1058 auth_user
= proxy_auth_hash
->auth_user
;
1059 this->user(auth_user
);
1060 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
1062 debug(29, 9) ("found matching cache entry\n");
1063 assert(auth_user
->auth_type
== AUTH_NTLM
);
1064 /* get the existing entries details */
1065 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user
);
1066 debug(29, 9) ("Username to be used is %s\n", ntlm_user
->username());
1067 /* on ntlm auth we do not unlock the auth_user until the
1068 * connection is dropped. Thank MS for this quirk */
1069 auth_user
->expiretime
= current_time
.tv_sec
;
1075 case AUTHENTICATE_STATE_RESPONSE
:
1076 /* auth-challenge pair cache miss. We've just got the response from the helper */
1077 /*add to cache and let them through */
1078 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
1079 /* this connection is authenticated */
1080 debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n",
1081 ntlm_request
->authchallenge
,
1082 ntlm_request
->ntlmauthenticate
,
1083 ntlm_user
->username());
1084 /* cache entries have authenticateauthheaderchallengestring */
1085 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
1086 ntlm_request
->ntlmauthenticate
,
1087 ntlm_request
->authchallenge
);
1088 /* see if this is an existing user with a different proxy_auth
1091 if ((usernamehash
= static_cast<AuthUserHashPointer
*>(hash_lookup(proxy_auth_username_cache
, ntlm_user
->username())))
1093 while ((usernamehash
->user()->auth_type
!= auth_user
->auth_type
) && (usernamehash
->next
) && !authenticateNTLMcmpUsername(dynamic_cast<ntlm_user_t
*>(usernamehash
->user()), ntlm_user
)
1095 usernamehash
= static_cast<AuthUserHashPointer
*>(usernamehash
->next
);
1096 if (usernamehash
->user()->auth_type
== auth_user
->auth_type
) {
1098 * add another link from the new proxy_auth to the
1099 * auth_user structure and update the information */
1100 assert(proxy_auth_hash
== NULL
);
1101 authenticateProxyAuthCacheAddLink(ntlmhash
, usernamehash
->user());
1102 /* we can't seamlessly recheck the username due to the
1103 * challenge nature of the protocol. Just free the
1104 * temporary auth_user */
1105 authenticateAuthUserMerge(auth_user
, usernamehash
->user());
1106 auth_user
= usernamehash
->user();
1107 this->user(auth_user
);
1110 /* store user in hash's */
1111 auth_user
->addToNameCache();
1112 authenticateProxyAuthCacheAddLink(ntlmhash
, auth_user
);
1115 /* set these to now because this is either a new login from an
1116 * existing user or a new user */
1117 auth_user
->expiretime
= current_time
.tv_sec
;
1121 case AUTHENTICATE_STATE_DONE
:
1122 fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n");
1125 case AUTHENTICATE_STATE_FAILED
:
1126 /* we've failed somewhere in authentication */
1127 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm failed. %s\n", proxy_auth
);
1134 AuthNTLMUserRequest::AuthNTLMUserRequest() : ntlmnegotiate(NULL
), authchallenge(NULL
), ntlmauthenticate(NULL
),
1135 authserver(NULL
), auth_state(AUTHENTICATE_STATE_NONE
),
1136 authserver_deferred(0), conn(NULL
), _theUser(NULL
)
1139 AuthNTLMUserRequest::~AuthNTLMUserRequest()
1142 xfree(ntlmnegotiate
);
1145 xfree(authchallenge
);
1147 if (ntlmauthenticate
)
1148 xfree(ntlmauthenticate
);
1150 if (authserver
!= NULL
&& authserver_deferred
) {
1151 debug(29, 9) ("authenticateNTLMRequestFree: releasing server '%p'\n", authserver
);
1152 helperStatefulReleaseServer(authserver
);
1158 NTLMUser::deleteSelf() const
1163 NTLMUser::NTLMUser (AuthConfig
*config
) : AuthUser (config
)
1165 proxy_auth_list
.head
= proxy_auth_list
.tail
= NULL
;
1169 ntlmScheme::createConfig()