3 * $Id: auth_ntlm.cc,v 1.59 2006/07/07 19:10:30 serassio Exp $
5 * DEBUG: section 29 NTLM Authenticator
6 * AUTHOR: Robert Collins, Henrik Nordstrom, Francesco Chemolli
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"
44 #include "CacheManager.h"
46 #include "client_side.h"
47 #include "HttpReply.h"
48 #include "HttpRequest.h"
49 /* TODO remove this include */
50 #include "ntlmScheme.h"
54 authenticateNTLMReleaseServer(auth_user_request_t
* auth_user_request
);
58 authenticateStateFree(authenticateStateData
* r
)
64 static HLPSCB authenticateNTLMHandleReply
;
65 static AUTHSSTATS authenticateNTLMStats
;
67 static statefulhelper
*ntlmauthenticators
= NULL
;
69 CBDATA_TYPE(authenticateStateData
);
71 static int authntlm_initialised
= 0;
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) ("ntlmScheme::done: shutting down NTLM authentication.\n");
92 if (ntlmauthenticators
)
93 helperStatefulShutdown(ntlmauthenticators
);
95 authntlm_initialised
= 0;
100 if (ntlmauthenticators
)
101 helperStatefulFree(ntlmauthenticators
);
103 ntlmauthenticators
= NULL
;
105 debug(29, 2) ("ntlmScheme::done: NTLM authentication Shutdown.\n");
108 /* free any allocated configuration details */
110 AuthNTLMConfig::done()
113 wordlistDestroy(&authenticate
);
117 AuthNTLMConfig::dump(StoreEntry
* entry
, const char *name
, AuthConfig
* scheme
)
119 wordlist
*list
= authenticate
;
120 storeAppendPrintf(entry
, "%s %s", name
, "ntlm");
122 while (list
!= NULL
) {
123 storeAppendPrintf(entry
, " %s", list
->key
);
127 storeAppendPrintf(entry
, "\n%s ntlm children %d\n",
128 name
, authenticateChildren
);
129 storeAppendPrintf(entry
, "%s %s keep_alive %s\n", name
, "ntlm", keep_alive
? "on" : "off");
133 AuthNTLMConfig::AuthNTLMConfig() : authenticateChildren(5), keep_alive(1)
137 AuthNTLMConfig::parse(AuthConfig
* scheme
, int n_configured
, char *param_str
)
139 if (strcasecmp(param_str
, "program") == 0) {
141 wordlistDestroy(&authenticate
);
143 parse_wordlist(&authenticate
);
145 requirePathnameExists("authparam ntlm program", authenticate
->key
);
146 } else if (strcasecmp(param_str
, "children") == 0) {
147 parse_int(&authenticateChildren
);
148 } else if (strcasecmp(param_str
, "keep_alive") == 0) {
149 parse_onoff(&keep_alive
);
151 debug(29, 0) ("AuthNTLMConfig::parse: unrecognised ntlm auth scheme parameter '%s'\n", param_str
);
155 * disable client side request pipelining. There is a race with
156 * NTLM when the client sends a second request on an NTLM
157 * connection before the authenticate challenge is sent. With
158 * this patch, the client may fail to authenticate, but squid's
159 * state will be preserved. Caveats: this should be a post-parse
160 * test, but that can wait for the modular parser to be integrated.
163 Config
.onoff
.pipeline_prefetch
= 0;
167 AuthNTLMConfig::type() const
169 return ntlmScheme::GetInstance().type();
172 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
175 AuthNTLMConfig::init(AuthConfig
* scheme
)
180 if (!ntlm_user_hash_pool
)
182 ntlm_user_hash_pool
= new MemAllocatorProxy("NTLM Header Hash Data", sizeof(struct ProxyAuthCachePointer
));
186 authntlm_initialised
= 1;
188 if (ntlmauthenticators
== NULL
)
189 ntlmauthenticators
= helperStatefulCreate("ntlmauthenticator");
191 if (!proxy_auth_cache
)
192 proxy_auth_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
194 assert(proxy_auth_cache
);
196 ntlmauthenticators
->cmdline
= authenticate
;
198 ntlmauthenticators
->n_to_start
= authenticateChildren
;
200 ntlmauthenticators
->ipc_type
= IPC_STREAM
;
202 helperStatefulOpenServers(ntlmauthenticators
);
204 CBDATA_INIT_TYPE(authenticateStateData
);
209 AuthNTLMConfig::registerWithCacheManager(CacheManager
& manager
)
211 manager
.registerAction("ntlmauthenticator",
212 "NTLM User Authenticator Stats",
213 authenticateNTLMStats
, 0, 1);
217 AuthNTLMConfig::active() const
219 return authntlm_initialised
== 1;
223 AuthNTLMConfig::configured() const
225 if ((authenticate
!= NULL
) && (authenticateChildren
!= 0)) {
226 debug(29, 9) ("AuthNTLMConfig::configured: returning configured\n");
230 debug(29, 9) ("AuthNTLMConfig::configured: returning unconfigured\n");
235 /* See AuthUserRequest.cc::authenticateDirection for return values */
237 AuthNTLMUserRequest::module_direction()
239 /* null auth_user is checked for by authenticateDirection */
241 if (waiting
|| client_blob
)
242 return -1; /* need helper response to continue */
244 switch (auth_state
) {
246 /* no progress at all. */
248 case AUTHENTICATE_STATE_NONE
:
249 debug(29, 1) ("AuthNTLMUserRequest::direction: called before NTLM Authenticate for request %p!. Report a bug to squid-dev.\n",this);
250 return -2; /* error */
252 case AUTHENTICATE_STATE_FAILED
:
253 return -2; /* error */
256 case AUTHENTICATE_STATE_IN_PROGRESS
:
258 return 1; /* send to client */
260 case AUTHENTICATE_STATE_FINISHED
:
261 return 0; /* do nothing */
263 case AUTHENTICATE_STATE_DONE
:
264 return 0; /* do nothing */
266 case AUTHENTICATE_STATE_INITIAL
:
267 debug(29, 1) ("AuthNTLMUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL\n");
275 AuthNTLMConfig::fixHeader(auth_user_request_t
*auth_user_request
, HttpReply
*rep
, http_hdr_type type
, HttpRequest
* request
)
277 AuthNTLMUserRequest
*ntlm_request
;
282 /* New request, no user details */
283 if (auth_user_request
== NULL
) {
284 debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM'\n", type
);
285 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
288 /* drop the connection */
289 rep
->header
.delByName("keep-alive");
290 request
->flags
.proxy_keepalive
= 0;
293 ntlm_request
= dynamic_cast<AuthNTLMUserRequest
*>(auth_user_request
);
295 switch (ntlm_request
->auth_state
) {
297 case AUTHENTICATE_STATE_FAILED
:
298 /* here it makes sense to drop the connection, as auth is
299 * tied to it, even if MAYBE the client could handle it - Kinkie */
300 rep
->header
.delByName("keep-alive");
301 request
->flags
.proxy_keepalive
= 0;
304 case AUTHENTICATE_STATE_FINISHED
:
305 /* Special case: authentication finished OK but disallowed by ACL.
306 * Need to start over to give the client another chance.
310 case AUTHENTICATE_STATE_NONE
:
311 /* semantic change: do not drop the connection.
312 * 2.5 implementation used to keep it open - Kinkie */
313 debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM'\n", type
);
314 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
317 case AUTHENTICATE_STATE_IN_PROGRESS
:
318 /* we're waiting for a response from the client. Pass it the blob */
319 debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM %s'\n", type
, ntlm_request
->server_blob
);
320 httpHeaderPutStrf(&rep
->header
, type
, "NTLM %s", ntlm_request
->server_blob
);
321 request
->flags
.must_keepalive
= 1;
322 safe_free(ntlm_request
->server_blob
);
327 debug(29, 0) ("AuthNTLMConfig::fixHeader: state %d.\n", ntlm_request
->auth_state
);
328 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
333 NTLMUser::~NTLMUser()
335 debug(29, 5) ("NTLMUser::~NTLMUser: doing nothing to clearNTLM scheme data for '%p'\n",this);
338 static stateful_helper_callback_t
339 authenticateNTLMHandleReply(void *data
, void *lastserver
, char *reply
)
341 authenticateStateData
*r
= static_cast<authenticateStateData
*>(data
);
344 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
347 auth_user_request_t
*auth_user_request
;
350 AuthNTLMUserRequest
*ntlm_request
;
352 debug(29, 8) ("authenticateNTLMHandleReply: helper: '%p' sent us '%s'\n", lastserver
, reply
? reply
: "<NULL>");
353 valid
= cbdataReferenceValid(r
->data
);
356 debug(29, 1) ("authenticateNTLMHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver
);
357 cbdataReferenceDone(r
->data
);
358 authenticateStateFree(r
);
359 debug(29, 9) ("authenticateNTLMHandleReply: telling stateful helper : %d\n", S_HELPER_RELEASE
);
360 return S_HELPER_RELEASE
;
364 debug(29, 1) ("authenticateNTLMHandleReply: Helper '%p' crashed!.\n", lastserver
);
365 reply
= (char *)"BH Internal error";
368 auth_user_request
= r
->auth_user_request
;
369 assert(auth_user_request
!= NULL
);
370 ntlm_request
= dynamic_cast<AuthNTLMUserRequest
*>(auth_user_request
);
372 assert(ntlm_request
->waiting
);
373 ntlm_request
->waiting
= 0;
374 safe_free(ntlm_request
->client_blob
);
376 auth_user
= ntlm_request
->user();
377 assert(auth_user
!= NULL
);
378 assert(auth_user
->auth_type
== AUTH_NTLM
);
379 ntlm_user
= dynamic_cast<ntlm_user_t
*>(auth_user_request
->user());
381 if (ntlm_request
->authserver
== NULL
)
382 ntlm_request
->authserver
= static_cast<helper_stateful_server
*>(lastserver
);
384 assert(ntlm_request
->authserver
== lastserver
);
386 /* seperate out the useful data */
387 blob
= strchr(reply
, ' ');
392 if (strncasecmp(reply
, "TT ", 3) == 0) {
393 /* we have been given a blob to send to the client */
394 safe_free(ntlm_request
->server_blob
);
395 ntlm_request
->server_blob
= xstrdup(blob
);
396 ntlm_request
->auth_state
= AUTHENTICATE_STATE_IN_PROGRESS
;
397 auth_user_request
->denyMessage("Authenication in progress");
398 debug(29, 4) ("authenticateNTLMHandleReply: Need to challenge the client with a server blob '%s'\n", blob
);
399 result
= S_HELPER_RESERVE
;
400 } else if (strncasecmp(reply
, "AF ", 3) == 0) {
401 /* we're finished, release the helper */
402 ntlm_user
->username(blob
);
403 auth_user_request
->denyMessage("Login successful");
404 safe_free(ntlm_request
->server_blob
);
405 authenticateNTLMReleaseServer(ntlm_request
);
406 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FINISHED
;
408 result
= S_HELPER_RELEASE
;
409 debug(29, 4) ("authenticateNTLMHandleReply: Successfully validated user via NTLM. Username '%s'\n", blob
);
410 } else if (strncasecmp(reply
, "NA ", 3) == 0) {
411 /* authentication failure (wrong password, etc.) */
412 auth_user_request
->denyMessage(blob
);
413 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FAILED
;
414 safe_free(ntlm_request
->server_blob
);
415 authenticateNTLMReleaseServer(ntlm_request
);
416 result
= S_HELPER_RELEASE
;
417 debug(29, 4) ("authenticateNTLMHandleReply: Failed validating user via NTLM. Error returned '%s'\n", blob
);
418 } else if (strncasecmp(reply
, "BH ", 3) == 0) {
419 /* TODO kick off a refresh process. This can occur after a YR or after
420 * a KK. If after a YR release the helper and resubmit the request via
421 * Authenticate NTLM start.
422 * If after a KK deny the user's request w/ 407 and mark the helper as
424 auth_user_request
->denyMessage(blob
);
425 ntlm_request
->auth_state
= AUTHENTICATE_STATE_FAILED
;
426 safe_free(ntlm_request
->server_blob
);
427 authenticateNTLMReleaseServer(ntlm_request
);
428 result
= S_HELPER_RELEASE
;
429 debug(29, 1) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply
);
432 fatalf("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply
);
435 r
->handler(r
->data
, NULL
);
436 cbdataReferenceDone(r
->data
);
437 authenticateStateFree(r
);
438 debug(29, 9) ("authenticateNTLMHandleReply: telling stateful helper : %d\n", result
);
443 authenticateNTLMStats(StoreEntry
* sentry
)
445 storeAppendPrintf(sentry
, "NTLM Authenticator Statistics:\n");
446 helperStatefulStats(sentry
, ntlmauthenticators
);
450 /* send the initial data to a stateful ntlm authenticator module */
452 AuthNTLMUserRequest::module_start(RH
* handler
, void *data
)
454 authenticateStateData
*r
= NULL
;
455 static char buf
[8192];
456 ntlm_user_t
*ntlm_user
;
457 auth_user_t
*auth_user
= user();
462 assert(auth_user
->auth_type
== AUTH_NTLM
);
464 ntlm_user
= dynamic_cast<ntlm_user_t
*>(user());
466 debug(29, 8) ("AuthNTLMUserRequest::module_start: auth state is '%d'\n", auth_state
);
468 if (ntlmConfig
.authenticate
== NULL
) {
469 debug(29, 0) ("AuthNTLMUserRequest::module_start: no NTLM program specified.");
474 r
= cbdataAlloc(authenticateStateData
);
475 r
->handler
= handler
;
476 r
->data
= cbdataReference(data
);
477 r
->auth_user_request
= this;
482 if (auth_state
== AUTHENTICATE_STATE_INITIAL
) {
483 snprintf(buf
, 8192, "YR %s\n", client_blob
); //CHECKME: can ever client_blob be 0 here?
485 snprintf(buf
, 8192, "KK %s\n", client_blob
);
490 safe_free(client_blob
);
491 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, authserver
);
494 /* clear the NTLM helper of being reserved for future requests */
496 authenticateNTLMReleaseServer(auth_user_request_t
* auth_user_request
)
498 AuthNTLMUserRequest
*ntlm_request
;
499 assert(auth_user_request
->user()->auth_type
== AUTH_NTLM
);
500 ntlm_request
= dynamic_cast< AuthNTLMUserRequest
*>(auth_user_request
);
501 debug(29, 9) ("authenticateNTLMReleaseServer: releasing server '%p'\n", ntlm_request
->authserver
);
502 /* is it possible for the server to be NULL? hno seems to think so.
503 * Let's see what happens, might segfault in helperStatefulReleaseServer
504 * if it does. I leave it like this not to cover possibly problematic
505 * code-paths. Kinkie */
506 helperStatefulReleaseServer(ntlm_request
->authserver
);
507 ntlm_request
->authserver
= NULL
;
510 /* clear any connection related authentication details */
512 AuthNTLMUserRequest::onConnectionClose(ConnStateData
*connection
)
514 assert(connection
!= NULL
);
516 debug(29,8)("AuthNTLMUserRequest::onConnectionClose: closing connection '%p' (this is '%p')\n",connection
,this);
518 if (connection
->auth_user_request
== NULL
) {
519 debug(29,8)("AuthNTLMUserRequest::onConnectionClose: no auth_user_request\n");
523 if (authserver
!= NULL
)
524 authenticateNTLMReleaseServer(this);
526 /* unlock the connection based lock */
527 debug(29, 9) ("AuthNTLMUserRequest::onConnectionClose: Unlocking auth_user from the connection '%p'.\n",connection
);
529 /* This still breaks the abstraction, but is at least read only now.
530 * If needed, this could be ignored, as the conn deletion will also unlock
531 * the auth user request.
535 connection
->auth_user_request
= NULL
;
539 * Decode a NTLM [Proxy-]Auth string, placing the results in the passed
540 * Auth_user structure.
543 AuthNTLMConfig::decode(char const *proxy_auth
)
545 NTLMUser
*newUser
= new NTLMUser(&ntlmConfig
);
546 AuthNTLMUserRequest
*auth_user_request
= new AuthNTLMUserRequest ();
547 assert(auth_user_request
->user() == NULL
);
548 auth_user_request
->user(newUser
);
549 auth_user_request
->user()->auth_type
= AUTH_NTLM
;
550 auth_user_request
->user()->addRequest(auth_user_request
);
552 /* all we have to do is identify that it's NTLM - the helper does the rest */
553 debug(29, 9) ("AuthNTLMConfig::decode: NTLM authentication\n");
554 return auth_user_request
;
558 AuthNTLMUserRequest::authenticated() const
560 if (auth_state
== AUTHENTICATE_STATE_FINISHED
) {
561 debug(29, 9) ("AuthNTLMUserRequest::authenticated: user authenticated.\n");
565 debug(29, 9) ("AuthNTLMUserRequest::authenticated: user not fully authenticated.\n");
571 AuthNTLMUserRequest::authenticate(HttpRequest
* request
, ConnStateData::Pointer conn
, http_hdr_type type
)
573 const char *proxy_auth
, *blob
;
575 //ProxyAuthCachePointer *proxy_auth_hash = NULL;
576 auth_user_hash_pointer
*usernamehash
;
578 /* TODO: rename this!! */
579 auth_user_t
*local_auth_user
;
580 ntlm_user_t
*ntlm_user
;
582 local_auth_user
= user();
583 assert(local_auth_user
);
584 assert(local_auth_user
->auth_type
== AUTH_NTLM
);
585 ntlm_user
= dynamic_cast<ntlm_user_t
*>(local_auth_user
);
588 /* Check that we are in the client side, where we can generate
591 if (conn
.getRaw() == NULL
) {
592 auth_state
= AUTHENTICATE_STATE_FAILED
;
593 debug(29, 1) ("AuthNTLMUserRequest::authenticate: attempt to perform authentication without a connection!\n");
598 debug(29, 1) ("AuthNTLMUserRequest::authenticate: waiting for helper reply!\n");
603 debug(29,2)("AuthNTLMUserRequest::authenticate: need to challenge client '%s'!\n", server_blob
);
608 proxy_auth
= request
->header
.getStr(type
);
610 /* locate second word */
613 while (xisspace(*blob
) && *blob
)
616 while (!xisspace(*blob
) && *blob
)
619 while (xisspace(*blob
) && *blob
)
622 switch (auth_state
) {
624 case AUTHENTICATE_STATE_NONE
:
625 /* we've recieved a ntlm request. pass to a helper */
626 debug(29, 9) ("AuthNTLMUserRequest::authenticate: auth state ntlm none. Received blob: '%s'\n", proxy_auth
);
627 auth_state
= AUTHENTICATE_STATE_INITIAL
;
628 safe_free(client_blob
);
629 client_blob
=xstrdup(blob
);
630 conn
->auth_type
= AUTH_NTLM
;
631 conn
->auth_user_request
= this;
641 case AUTHENTICATE_STATE_INITIAL
:
642 debug(29,1)("AuthNTLMUserRequest::authenticate: need to ask helper\n");
649 case AUTHENTICATE_STATE_IN_PROGRESS
:
650 /* we should have received a blob from the client. Hand it off to
652 safe_free(client_blob
);
654 client_blob
= xstrdup (blob
);
660 case AUTHENTICATE_STATE_FINISHED
:
661 /* connection is authenticated */
662 debug(29, 4) ("AuthNTLMUserRequest::authenticate: authenticated user %s\n", ntlm_user
->username());
664 /* see if this is an existing user with a different proxy_auth
666 usernamehash
= static_cast<AuthUserHashPointer
*>(hash_lookup(proxy_auth_username_cache
, ntlm_user
->username()));
668 while (usernamehash
&& (usernamehash
->user()->auth_type
!= AUTH_NTLM
|| strcmp(usernamehash
->user()->username(), ntlm_user
->username()) != 0))
669 usernamehash
= static_cast<AuthUserHashPointer
*>(usernamehash
->next
);
672 /* we can't seamlessly recheck the username due to the
673 * challenge-response nature of the protocol.
674 * Just free the temporary auth_user */
675 usernamehash
->user()->absorb(local_auth_user
);
676 //authenticateAuthUserMerge(local_auth_user, usernamehash->user());
677 local_auth_user
= usernamehash
->user();
678 _auth_user
= local_auth_user
;
680 /* store user in hash's */
681 local_auth_user
->addToNameCache();
682 // authenticateUserNameCacheAdd(local_auth_user);
685 /* set these to now because this is either a new login from an
686 * existing user or a new user */
687 local_auth_user
->expiretime
= current_time
.tv_sec
;
689 authenticateNTLMReleaseServer(this);
691 auth_state
= AUTHENTICATE_STATE_DONE
;
697 case AUTHENTICATE_STATE_DONE
:
698 fatal("AuthNTLMUserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n");
702 case AUTHENTICATE_STATE_FAILED
:
703 /* we've failed somewhere in authentication */
704 debug(29, 9) ("AuthNTLMUserRequest::authenticate: auth state ntlm failed. %s\n", proxy_auth
);
714 AuthNTLMUserRequest::AuthNTLMUserRequest() :
715 conn(NULL
), auth_state(AUTHENTICATE_STATE_NONE
),
724 AuthNTLMUserRequest::~AuthNTLMUserRequest()
726 safe_free(server_blob
);
727 safe_free(client_blob
);
729 if (authserver
!= NULL
) {
730 debug(29, 9) ("AuthNTLMUserRequest::~AuthNTLMUserRequest: releasing server '%p'\n", authserver
);
731 helperStatefulReleaseServer(authserver
);
737 NTLMUser::deleteSelf() const
742 NTLMUser::NTLMUser (AuthConfig
*config
) : AuthUser (config
)
744 proxy_auth_list
.head
= proxy_auth_list
.tail
= NULL
;
748 ntlmScheme::createConfig()
754 AuthNTLMUserRequest::connLastHeader()