3 * $Id: auth_ntlm.cc,v 1.2 2001/01/08 19:36:27 hno Exp $
5 * DEBUG: section 29 NTLM Authenticator
6 * AUTHOR: Robert Collins
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by the
14 * National Science Foundation. Squid is Copyrighted (C) 1998 by
15 * the Regents of the University of California. Please see the
16 * COPYRIGHT file for full details. Squid incorporates software
17 * developed and/or copyrighted by other sources. Please see the
18 * 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"
45 authenticateStateFree(authenticateStateData
* r
)
51 static HLPSCB authenticateNTLMHandleReply
;
52 static HLPSCB authenticateNTLMHandleplaceholder
;
53 static AUTHSACTIVE authenticateNTLMActive
;
54 static AUTHSAUTHED authNTLMAuthenticated
;
55 static AUTHSAUTHUSER authenticateNTLMAuthenticateUser
;
56 static AUTHSFIXERR authenticateNTLMFixErrorHeader
;
57 static AUTHSFREE authenticateNTLMFreeUser
;
58 static AUTHSDIRECTION authenticateNTLMDirection
;
59 static AUTHSDECODE authenticateDecodeNTLMAuth
;
60 static AUTHSDUMP authNTLMCfgDump
;
61 static AUTHSFREECONFIG authNTLMFreeConfig
;
62 static AUTHSINIT authNTLMInit
;
63 static AUTHSONCLOSEC authenticateNTLMOnCloseConnection
;
64 static AUTHSUSERNAME authenticateNTLMUsername
;
65 static AUTHSREQFREE authNTLMAURequestFree
;
66 static AUTHSPARSE authNTLMParse
;
67 static AUTHSSTART authenticateNTLMStart
;
68 static AUTHSSTATS authenticateNTLMStats
;
69 static AUTHSSHUTDOWN authNTLMDone
;
71 /* helper callbacks to handle per server state data */
72 static HLPSAVAIL authenticateNTLMHelperServerAvailable
;
73 static HLPSONEQ authenticateNTLMHelperServerOnEmpty
;
75 static statefulhelper
*ntlmauthenticators
= NULL
;
77 CBDATA_TYPE(authenticateStateData
);
79 static int authntlm_initialised
= 0;
81 MemPool
*ntlm_helper_state_pool
= NULL
;
82 MemPool
*ntlm_user_pool
= NULL
;
83 MemPool
*ntlm_request_pool
= NULL
;
84 static auth_ntlm_config
*ntlmConfig
= NULL
;
86 static hash_table
*proxy_auth_cache
= NULL
;
97 // memPoolDestroy(ufs_state_pool);
99 if (ntlmauthenticators
)
100 helperStatefulShutdown(ntlmauthenticators
);
101 authntlm_initialised
= 0;
104 helperStatefulFree(ntlmauthenticators
);
105 ntlmauthenticators
= NULL
;
106 memPoolDestroy(ntlm_helper_state_pool
);
107 memPoolDestroy(ntlm_request_pool
);
108 memPoolDestroy(ntlm_user_pool
);
112 /* free any allocated configuration details */
114 authNTLMFreeConfig(authScheme
* scheme
)
116 if (ntlmConfig
== NULL
)
118 assert(ntlmConfig
== scheme
->scheme_data
);
119 if (ntlmConfig
->authenticate
)
120 wordlistDestroy(&ntlmConfig
->authenticate
);
126 authNTLMCfgDump(StoreEntry
* entry
, const char *name
, authScheme
* scheme
)
128 auth_ntlm_config
*config
= scheme
->scheme_data
;
129 wordlist
*list
= config
->authenticate
;
130 storeAppendPrintf(entry
, "%s %s", name
, "ntlm");
131 while (list
!= NULL
) {
132 storeAppendPrintf(entry
, " %s", list
->key
);
135 storeAppendPrintf(entry
, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n",
136 name
, "ntlm", config
->authenticateChildren
,
137 name
, "ntlm", config
->challengeuses
,
138 name
, "ntlm", config
->challengelifetime
);
143 authNTLMParse(authScheme
* scheme
, int n_configured
, char *param_str
)
145 if (scheme
->scheme_data
== NULL
) {
146 assert(ntlmConfig
== NULL
);
147 /* this is the first param to be found */
148 scheme
->scheme_data
= xmalloc(sizeof(auth_ntlm_config
));
149 memset(scheme
->scheme_data
, 0, sizeof(auth_ntlm_config
));
150 ntlmConfig
= scheme
->scheme_data
;
151 ntlmConfig
->authenticateChildren
= 5;
152 ntlmConfig
->challengeuses
= 0;
153 ntlmConfig
->challengelifetime
= 60;
155 ntlmConfig
= scheme
->scheme_data
;
156 if (strcasecmp(param_str
, "program") == 0) {
157 parse_wordlist(&ntlmConfig
->authenticate
);
158 requirePathnameExists("authparam ntlm program", ntlmConfig
->authenticate
->key
);
159 } else if (strcasecmp(param_str
, "children") == 0) {
160 parse_int(&ntlmConfig
->authenticateChildren
);
161 } else if (strcasecmp(param_str
, "max_challenge_reuses") == 0) {
162 parse_int(&ntlmConfig
->challengeuses
);
163 } else if (strcasecmp(param_str
, "max_challenge_lifetime") == 0) {
164 parse_time_t(&ntlmConfig
->challengelifetime
);
166 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str
);
172 authSchemeSetup_ntlm(authscheme_entry_t
* authscheme
)
175 static int ntlminit
= 0;
177 assert(!authntlm_initialised
);
178 authscheme
->Active
= authenticateNTLMActive
;
179 authscheme
->parse
= authNTLMParse
;
180 authscheme
->dump
= authNTLMCfgDump
;
181 authscheme
->requestFree
= authNTLMAURequestFree
;
182 authscheme
->freeconfig
= authNTLMFreeConfig
;
183 authscheme
->init
= authNTLMInit
;
184 authscheme
->authAuthenticate
= authenticateNTLMAuthenticateUser
;
185 authscheme
->authenticated
= authNTLMAuthenticated
;
186 authscheme
->authFixHeader
= authenticateNTLMFixErrorHeader
;
187 authscheme
->FreeUser
= authenticateNTLMFreeUser
;
188 authscheme
->authStart
= authenticateNTLMStart
;
189 authscheme
->authStats
= authenticateNTLMStats
;
190 authscheme
->authUserUsername
= authenticateNTLMUsername
;
191 authscheme
->getdirection
= authenticateNTLMDirection
;
192 authscheme
->decodeauth
= authenticateDecodeNTLMAuth
;
193 authscheme
->donefunc
= authNTLMDone
;
194 authscheme
->oncloseconnection
= authenticateNTLMOnCloseConnection
;
197 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
200 authNTLMInit(authScheme
* scheme
)
202 static int ntlminit
= 0;
203 if (ntlmConfig
->authenticate
) {
204 if (!ntlm_helper_state_pool
)
205 ntlm_helper_state_pool
= memPoolCreate("NTLM Helper State data", sizeof(ntlm_helper_state_t
));
207 ntlm_user_pool
= memPoolCreate("NTLM Scheme User Data", sizeof(ntlm_user_t
));
208 if (!ntlm_request_pool
)
209 ntlm_request_pool
= memPoolCreate("NTLM Scheme Request Data", sizeof(ntlm_request_t
));
210 authntlm_initialised
= 1;
211 if (ntlmauthenticators
== NULL
)
212 ntlmauthenticators
= helperStatefulCreate("ntlmauthenticator");
213 if (!proxy_auth_cache
)
214 proxy_auth_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
215 assert(proxy_auth_cache
);
216 ntlmauthenticators
->cmdline
= ntlmConfig
->authenticate
;
217 ntlmauthenticators
->n_to_start
= ntlmConfig
->authenticateChildren
;
218 ntlmauthenticators
->ipc_type
= IPC_TCP_SOCKET
;
219 ntlmauthenticators
->datapool
= ntlm_helper_state_pool
;
220 ntlmauthenticators
->IsAvailable
= authenticateNTLMHelperServerAvailable
;
221 ntlmauthenticators
->OnEmptyQueue
= authenticateNTLMHelperServerOnEmpty
;
222 helperStatefulOpenServers(ntlmauthenticators
);
223 /* TODO: In here send the initial YR to preinitialise the challenge cache */
224 /* Think about this... currently we ask when the challenge is needed. Better? */
226 cachemgrRegister("ntlmauthenticator",
227 "User NTLM Authenticator Stats",
228 authenticateNTLMStats
, 0, 1);
231 CBDATA_INIT_TYPE(authenticateStateData
);
236 authenticateNTLMActive()
238 if ((ntlmConfig
!= NULL
) && (ntlmConfig
->authenticate
!= NULL
) &&
239 (ntlmConfig
->authenticateChildren
!= 0) && (ntlmConfig
->challengeuses
> -1)
240 && (ntlmConfig
->challengelifetime
>-1))
248 authenticateNTLMDirection(auth_user_request_t
* auth_user_request
)
250 ntlm_request_t
*ntlm_request
= auth_user_request
->scheme_data
;
251 /* null auth_user is checked for by authenticateDirection */
252 switch (ntlm_request
->auth_state
) {
253 case AUTHENTICATE_STATE_NONE
: /* no progress at all. */
254 debug(28, 1) ("authenticateNTLMDirection: called before NTLM Authenticate!. Report a bug to squid-dev.\n");
256 case AUTHENTICATE_STATE_NEGOTIATE
: /* send to helper */
257 case AUTHENTICATE_STATE_RESPONSE
: /*send to helper */
259 case AUTHENTICATE_STATE_CHALLENGE
: /* send to client */
261 case AUTHENTICATE_STATE_DONE
: /* do nothing.. */
268 * Send the authenticate error header(s). Note: IE has a bug and the NTLM header
269 * must be first. To ensure that, the configure use --enable-auth=ntlm, anything
273 authenticateNTLMFixErrorHeader(auth_user_request_t
* auth_user_request
, HttpReply
* rep
, http_hdr_type type
, request_t
* request
)
275 ntlm_request_t
*ntlm_request
;
276 if (ntlmConfig
->authenticate
) {
277 /* New request, no user details */
278 if (auth_user_request
== NULL
) {
279 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type
);
280 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
281 /* drop the connection */
282 httpHeaderDelByName(&rep
->header
, "keep-alive");
283 /* NTLM has problems if the initial connection is not dropped
284 * I haven't checked the RFC compliance of this hack - RBCollins */
285 request
->flags
.proxy_keepalive
= 0;
287 ntlm_request
= auth_user_request
->scheme_data
;
288 switch (ntlm_request
->auth_state
) {
289 case AUTHENTICATE_STATE_NONE
:
290 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type
);
291 httpHeaderPutStrf(&rep
->header
, type
, "NTLM");
292 /* drop the connection */
293 httpHeaderDelByName(&rep
->header
, "keep-alive");
294 /* NTLM has problems if the initial connection is not dropped
295 * I haven't checked the RFC compliance of this hack - RBCollins */
296 request
->flags
.proxy_keepalive
= 0;
298 case AUTHENTICATE_STATE_CHALLENGE
:
299 /* we are 'waiting' for a response */
300 /* pass the challenge to the client */
301 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type
, ntlm_request
->authchallenge
);
302 httpHeaderPutStrf(&rep
->header
, type
, "NTLM %s", ntlm_request
->authchallenge
);
305 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request
->auth_state
);
306 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
313 authNTLMRequestFree(ntlm_request_t
* ntlm_request
)
317 if (ntlm_request
->ntlmnegotiate
)
318 xfree(ntlm_request
->ntlmnegotiate
);
319 if (ntlm_request
->authchallenge
)
320 xfree(ntlm_request
->authchallenge
);
321 if (ntlm_request
->ntlmauthenticate
)
322 xfree(ntlm_request
->ntlmauthenticate
);
323 memPoolFree(ntlm_request_pool
, ntlm_request
);
327 authNTLMAURequestFree(auth_user_request_t
* auth_user_request
)
329 if (auth_user_request
->scheme_data
)
330 authNTLMRequestFree((ntlm_request_t
*) auth_user_request
->scheme_data
);
331 auth_user_request
->scheme_data
= NULL
;
335 authenticateNTLMFreeUser(auth_user_t
* auth_user
)
337 dlink_node
*link
, *tmplink
;
338 ntlm_user_t
*ntlm_user
= auth_user
->scheme_data
;
339 auth_user_hash_pointer
*proxy_auth_hash
;
341 debug(29, 5) ("authenticateNTLMFreeUser: Clearing NTLM scheme data\n");
342 if (ntlm_user
->username
)
343 xfree(ntlm_user
->username
);
344 /* were they linked in by one or more proxy-authenticate headers */
345 link
= ntlm_user
->proxy_auth_list
.head
;
347 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%d'\n", link
->data
);
348 proxy_auth_hash
= link
->data
;
351 dlinkDelete(tmplink
, &ntlm_user
->proxy_auth_list
);
352 hash_remove_link(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
353 /* free the key (usually the proxy_auth header) */
354 xfree(proxy_auth_hash
->key
);
355 memFree(proxy_auth_hash
, MEM_AUTH_USER_HASH
);
357 memPoolFree(ntlm_user_pool
, ntlm_user
);
358 auth_user
->scheme_data
= NULL
;
361 static stateful_helper_callback_t
362 authenticateNTLMHandleplaceholder(void *data
, void *lastserver
, char *reply
)
364 authenticateStateData
*r
= data
;
365 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
367 /* we should only be called for placeholder requests - which have no reply string */
368 assert(reply
== NULL
);
369 assert(r
->auth_user_request
);
370 /* standard callback stuff */
371 valid
= cbdataValid(r
->data
);
372 cbdataUnlock(r
->data
);
373 /* call authenticateNTLMStart to retry this request */
374 debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n");
375 authenticateNTLMStart(r
->auth_user_request
, r
->handler
, r
->data
);
376 authenticateStateFree(r
);
380 static stateful_helper_callback_t
381 authenticateNTLMHandleReply(void *data
, void *lastserver
, char *reply
)
384 authenticateStatefulStateData
*r
= data
;
386 authenticateStateData
*r
= data
;
387 ntlm_helper_state_t
*helperstate
;
389 stateful_helper_callback_t result
= S_HELPER_UNKNOWN
;
391 void *nextserver
= NULL
;
394 auth_user_request_t
*auth_user_request
;
395 auth_user_t
*auth_user
;
396 ntlm_user_t
*ntlm_user
;
397 ntlm_request_t
*ntlm_request
;
398 debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%d' {%s}\n", lastserver
, reply
? reply
: "<NULL>");
399 valid
= cbdataValid(r
->data
);
400 cbdataUnlock(r
->data
);
403 /* seperate out the useful data */
404 if (strncasecmp(reply
, "TT ", 3) == 0) {
406 /* we have been given a Challenge */
407 /* we should check we weren't given an empty challenge */
409 result
= S_HELPER_RESERVE
;
411 /* copy the challenge to the state data */
412 helperstate
= helperStatefulServerGetData(lastserver
);
413 if (helperstate
== NULL
)
414 fatal("lost NTLm helper state! quitting\n");
415 helperstate
->challenge
= xstrndup(reply
, NTLM_CHALLENGE_SZ
+ 5);
416 helperstate
->challengeuses
= 0;
417 helperstate
->renewed
= squid_curtime
;
418 /* and we satisfy the request that happended on the refresh boundary */
419 /* note this code is now in two places FIXME */
420 assert(r
->auth_user_request
!= NULL
);
421 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
422 auth_user_request
= r
->auth_user_request
;
423 ntlm_request
= auth_user_request
->scheme_data
;
424 assert(ntlm_request
!= NULL
);
425 result
= S_HELPER_DEFER
;
427 nextserver
= lastserver
;
429 debug(29, 9) ("authenticateNTLMHandleReply: helper '%d'\n", lastserver
);
430 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
);
431 // auth_user->auth_data.ntlm_auth.auth_state = AUTHENTICATE_STATE_CHALLENGE;
432 ntlm_request
->authhelper
= lastserver
;
433 ntlm_request
->authchallenge
= xstrndup(reply
, NTLM_CHALLENGE_SZ
+ 5);
434 } else if (strncasecmp(reply
, "AF ", 3) == 0) {
435 /* we're finished, release the helper */
437 assert(r
->auth_user_request
!= NULL
);
438 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
439 auth_user_request
= r
->auth_user_request
;
440 assert(auth_user_request
->scheme_data
!= NULL
);
441 ntlm_request
= auth_user_request
->scheme_data
;
442 auth_user
= auth_user_request
->auth_user
;
443 ntlm_user
= auth_user_request
->auth_user
->scheme_data
;
444 assert(ntlm_user
!= NULL
);
445 result
= S_HELPER_RELEASE
;
446 /* we only expect OK when finishing the handshake */
447 assert(ntlm_request
->auth_state
== AUTHENTICATE_STATE_RESPONSE
);
448 ntlm_user
->username
= xstrndup(reply
, MAX_LOGIN_SZ
);
449 ntlm_request
->authhelper
= NULL
;
450 auth_user
->flags
.credentials_ok
= 1; /* login ok */
451 } else if (strncasecmp(reply
, "NA ", 3) == 0) {
452 /* TODO: only work with auth_user here if it exists */
453 assert(r
->auth_user_request
!= NULL
);
454 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
455 auth_user_request
= r
->auth_user_request
;
456 auth_user
= auth_user_request
->auth_user
;
457 assert(auth_user
!= NULL
);
458 ntlm_user
= auth_user
->scheme_data
;
459 ntlm_request
= auth_user_request
->scheme_data
;
460 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
461 /* todo: action of Negotiate state on error */
462 result
= S_HELPER_RELEASE
; /*some error has occured. no more requests */
463 ntlm_request
->authhelper
= NULL
;
464 auth_user
->flags
.credentials_ok
= 2; /* Login/Usercode failed */
465 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n");
466 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
467 if ((t
= strchr(reply
, ' '))) /* strip after a space */
469 } else if (strncasecmp(reply
, "BH ", 3) == 0) {
470 /* TODO kick off a refresh process. This can occur after a YR or after
471 * a KK. If after a YR release the helper and resubmit the request via
472 * Authenticate NTLM start.
473 * If after a KK deny the user's request w/ 407 and mark the helper as
475 assert(r
->auth_user_request
!= NULL
);
476 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
477 auth_user_request
= r
->auth_user_request
;
478 auth_user
= auth_user_request
->auth_user
;
479 assert(auth_user
!= NULL
);
480 ntlm_user
= auth_user
->scheme_data
;
481 ntlm_request
= auth_user_request
->scheme_data
;
482 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
483 result
= S_HELPER_RELEASE
; /*some error has occured. no more requests for
485 helperstate
= helperStatefulServerGetData(ntlm_request
->authhelper
);
486 ntlm_request
->authhelper
= NULL
;
487 if (ntlm_request
->auth_state
== AUTHENTICATE_STATE_NEGOTIATE
) {
488 /* The helper broke on YR. It automatically
490 auth_user
->flags
.credentials_ok
= 3; /* cannot process */
491 debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %d.\n", lastserver
);
492 /* mark it for starving */
493 helperstate
->starve
= 1;
494 /* resubmit the request. This helper is currently busy, so we will get
495 * a different one. */
496 authenticateNTLMStart(auth_user_request
, r
->handler
, r
->data
);
498 /* the helper broke on a KK */
499 /* first the standard KK stuff */
500 auth_user
->flags
.credentials_ok
= 2; /* Login/Usercode failed */
501 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n");
502 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
503 if ((t
= strchr(reply
, ' '))) /* strip after a space */
505 /* now we mark the helper for resetting. */
506 helperstate
->starve
= 1;
508 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
510 /* TODO: only work with auth_user here if it exists */
511 assert(r
->auth_user_request
!= NULL
);
512 assert(r
->auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
513 auth_user_request
= r
->auth_user_request
;
514 auth_user
= auth_user_request
->auth_user
;
515 assert(auth_user
!= NULL
);
516 ntlm_user
= auth_user
->scheme_data
;
517 ntlm_request
= auth_user_request
->scheme_data
;
518 assert((ntlm_user
!= NULL
) && (ntlm_request
!= NULL
));
519 debug(29, 1) ("authenticateNTLMHandleReply: Unsupported helper response, '%s'\n", reply
);
520 /* restart the authentication process */
521 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NONE
;
522 auth_user
->flags
.credentials_ok
= 3; /* cannot process */
523 ntlm_request
->authhelper
= NULL
;
526 fatal("authenticateNTLMHandleReply: called with no result string\n");
528 r
->handler(r
->data
, NULL
);
530 debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%d'.\n", lastserver
);
531 result
= S_HELPER_RELEASE
;
533 authenticateStateFree(r
);
534 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result
);
540 authenticateNTLMStateFree(authenticateNTLMStateData
* r
)
548 authenticateNTLMStats(StoreEntry
* sentry
)
550 storeAppendPrintf(sentry
, "NTLM Authenticator Statistics:\n");
551 helperStatefulStats(sentry
, ntlmauthenticators
);
554 /* is a particular challenge still valid ? */
556 authenticateNTLMValidChallenge(ntlm_helper_state_t
* helperstate
)
558 if (helperstate
->challenge
== NULL
)
563 /* does our policy call for changing the challenge now? */
565 authenticateNTLMChangeChallenge(ntlm_helper_state_t
* helperstate
)
567 /* don't check for invalid challenges just for expiry choices */
568 /* this is needed because we have to starve the helper until all old
569 * requests have been satisfied */
570 if (helperstate
->challengeuses
> ntlmConfig
->challengeuses
)
572 if (helperstate
->renewed
+ ntlmConfig
->challengelifetime
>= squid_curtime
)
577 /* send the initial data to a stateful ntlm authenticator module */
579 authenticateNTLMStart(auth_user_request_t
* auth_user_request
, RH
* handler
, void *data
)
582 authenticateStatefulStateData
*r
= NULL
;
584 authenticateStateData
*r
= NULL
;
585 helper_stateful_server
*server
;
586 ntlm_helper_state_t
*helperstate
;
588 char *sent_string
= NULL
;
589 ntlm_user_t
*ntlm_user
;
590 ntlm_request_t
*ntlm_request
;
591 auth_user_t
*auth_user
;
593 assert(auth_user_request
);
594 auth_user
= auth_user_request
->auth_user
;
595 ntlm_user
= auth_user
->scheme_data
;
596 ntlm_request
= auth_user_request
->scheme_data
;
598 assert(ntlm_request
);
601 assert(auth_user
->auth_type
= AUTH_NTLM
);
602 debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", ntlm_request
->auth_state
);
603 switch (ntlm_request
->auth_state
) {
604 case AUTHENTICATE_STATE_NEGOTIATE
:
605 sent_string
= xstrdup(ntlm_request
->ntlmnegotiate
);
607 case AUTHENTICATE_STATE_RESPONSE
:
608 sent_string
= xstrdup(ntlm_request
->ntlmauthenticate
);
609 assert(ntlm_request
->authhelper
);
610 debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%d'.\n", ntlm_request
->authhelper
);
613 fatal("Invalid authenticate state for NTLMStart");
616 while (!xisspace(*sent_string
)) /*trim NTLM */
619 while (xisspace(*sent_string
)) /*trim leading spaces */
622 debug(29, 9) ("authenticateNTLMStart: state '%d'\n", ntlm_request
->auth_state
);
623 debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string
);
624 if (ntlmConfig
->authenticate
== NULL
) {
625 debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string
);
626 // handler(data,0, NULL);
630 #ifdef NTLMHELPPROTOCOLV2
631 r
= CBDATA_ALLOC(authenticateStateData
, NULL
);
632 r
->handler
= handler
;
635 r
->auth_user_request
= auth_user_request
;
636 snprintf(buf
, 8192, "%s\n", sent_string
);
637 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, ntlm_request
->authhelper
);
638 debug(29, 9) ("authenticateNTLMstart: finished\n");
640 /* this is ugly TODO: move the challenge generation routines to their own function and
641 * tidy the logic up to make use of the efficiency we now have */
642 switch (ntlm_request
->auth_state
) {
643 case AUTHENTICATE_STATE_NEGOTIATE
:
645 * 1: get a helper server
646 * 2: does it have a challenge?
647 * 3: tell it to get a challenge, or give ntlmauthdone the challenge
649 server
= helperStatefulDefer(ntlmauthenticators
);
650 helperstate
= server
? helperStatefulServerGetData(server
) : NULL
;
651 while ((server
!= NULL
) &&
652 authenticateNTLMChangeChallenge(helperstate
)) {
653 /* flag this helper for challenge changing */
654 helperstate
->starve
= 1;
655 /* and release the deferred request */
656 helperStatefulReleaseServer(server
);
657 server
= helperStatefulDefer(ntlmauthenticators
);
659 helperstate
= helperStatefulServerGetData(server
);
662 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
664 ntlm_request
->authhelper
= server
;
665 /* tell the log what helper we have been given */
666 debug(29, 9) ("authenticateNTLMStart: helper '%d' assigned\n", server
);
667 /* valid challenge? */
668 if ((server
== NULL
) || !authenticateNTLMValidChallenge(helperstate
)) {
669 r
= CBDATA_ALLOC(authenticateStateData
, NULL
);
670 r
->handler
= handler
;
673 r
->auth_user_request
= auth_user_request
;
674 if (server
== NULL
) {
675 helperStatefulSubmit(ntlmauthenticators
, NULL
, authenticateNTLMHandleplaceholder
, r
, ntlm_request
->authhelper
);
677 snprintf(buf
, 8192, "YR\n");
678 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, ntlm_request
->authhelper
);
681 /* we have a valid challenge */
682 /* TODO: turn the below into a function and call from here and handlereply */
683 /* increment the challenge uses */
684 helperstate
->challengeuses
++;
685 /* assign the challenge */
686 ntlm_request
->authchallenge
=
687 xstrndup(helperstate
->challenge
, NTLM_CHALLENGE_SZ
+ 5);
692 case AUTHENTICATE_STATE_RESPONSE
:
693 r
= CBDATA_ALLOC(authenticateStateData
, NULL
);
694 r
->handler
= handler
;
697 r
->auth_user_request
= auth_user_request
;
698 snprintf(buf
, 8192, "KK %s\n", sent_string
);
699 helperStatefulSubmit(ntlmauthenticators
, buf
, authenticateNTLMHandleReply
, r
, ntlm_request
->authhelper
);
700 debug(29, 9) ("authenticateNTLMstart: finished\n");
703 fatal("Invalid authenticate state for NTLMStart");
708 /* callback used by stateful helper routines */
710 authenticateNTLMHelperServerAvailable(void *data
)
712 ntlm_helper_state_t
*statedata
= data
;
713 if (statedata
!= NULL
) {
714 if (statedata
->starve
) {
715 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
718 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
722 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
727 authenticateNTLMHelperServerOnEmpty(void *data
)
729 ntlm_helper_state_t
*statedata
= data
;
730 if (statedata
== NULL
)
732 if (statedata
->starve
) {
733 /* we have been starving the helper */
734 debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n");
735 statedata
->starve
= 0;
736 statedata
->challengeuses
= 0;
737 statedata
->renewed
= 0;
738 xfree(statedata
->challenge
);
739 statedata
->challenge
= NULL
;
744 /* clear the NTLM helper of being reserved for future requests */
746 authenticateNTLMReleasehelper(auth_user_request_t
* auth_user_request
)
748 ntlm_request_t
*ntlm_request
;
749 assert(auth_user_request
->auth_user
->auth_type
== AUTH_NTLM
);
750 assert(auth_user_request
->scheme_data
!= NULL
);
751 ntlm_request
= auth_user_request
->scheme_data
;
752 debug(29, 9) ("authenticateNTLMReleasehelper: releasing helper '%d'\n", ntlm_request
->authhelper
);
753 helperStatefulReleaseServer(ntlm_request
->authhelper
);
754 ntlm_request
->authhelper
= NULL
;
757 /* clear any connection related authentication details */
759 authenticateNTLMOnCloseConnection(ConnStateData
* conn
)
761 ntlm_request_t
*ntlm_request
;
762 assert(conn
!= NULL
);
763 if (conn
->auth_user_request
!= NULL
) {
764 assert(conn
->auth_user_request
->scheme_data
!= NULL
);
765 ntlm_request
= conn
->auth_user_request
->scheme_data
;
766 if (ntlm_request
->authhelper
!= NULL
)
767 authenticateNTLMReleasehelper(conn
->auth_user_request
);
768 /* unlock the connection based lock */
769 debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n");
770 authenticateAuthUserRequestUnlock(conn
->auth_user_request
);
771 conn
->auth_user_request
= NULL
;
775 /* authenticateUserUsername: return a pointer to the username in the */
777 authenticateNTLMUsername(auth_user_t
* auth_user
)
779 ntlm_user_t
*ntlm_user
= auth_user
->scheme_data
;
781 return ntlm_user
->username
;
787 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
788 * Auth_user structure.
792 authenticateDecodeNTLMAuth(auth_user_request_t
* auth_user_request
, const char *proxy_auth
)
795 assert(auth_user_request
->auth_user
== NULL
);
796 auth_user_request
->auth_user
= authenticateAuthUserNew("ntlm");
797 auth_user_request
->auth_user
->auth_type
= AUTH_NTLM
;
798 auth_user_request
->auth_user
->scheme_data
= memPoolAlloc(ntlm_user_pool
);
799 auth_user_request
->scheme_data
= memPoolAlloc(ntlm_request_pool
);
800 /* lock for the auth_user_request link */
801 authenticateAuthUserLock(auth_user_request
->auth_user
);
802 node
= dlinkNodeNew();
803 dlinkAdd(auth_user_request
, node
, &auth_user_request
->auth_user
->requests
);
805 /* all we have to do is identify that it's NTLM - the helper does the rest */
806 debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n");
811 authenticateNTLMcmpUsername(ntlm_user_t
* u1
, ntlm_user_t
* u2
)
813 return strcmp(u1
->username
, u2
->username
);
817 authenticateProxyAuthCacheAddLink(const char *key
, auth_user_t
* auth_user
)
819 auth_user_hash_pointer
*proxy_auth_hash
;
820 ntlm_user_t
*ntlm_user
;
822 memAllocate(MEM_AUTH_USER_HASH
);
823 proxy_auth_hash
->key
= xstrdup(key
);
824 proxy_auth_hash
->auth_user
= auth_user
;
825 ntlm_user
= auth_user
->scheme_data
;
826 dlinkAddTail(proxy_auth_hash
, &proxy_auth_hash
->link
,
827 &ntlm_user
->proxy_auth_list
);
828 hash_join(proxy_auth_cache
, (hash_link
*) proxy_auth_hash
);
833 authNTLMAuthenticated(auth_user_request_t
* auth_user_request
)
835 ntlm_request_t
*ntlm_request
= auth_user_request
->scheme_data
;
836 if (ntlm_request
->auth_state
== AUTHENTICATE_STATE_DONE
)
838 debug(29, 9) ("User not fully authenticated.\n");
843 static acl_proxy_auth_user
*
844 authenticateNTLMAuthenticateUser(void *data
, const char *proxy_auth
, ConnStateData
* conn
)
847 authenticateNTLMAuthenticateUser(auth_user_request_t
* auth_user_request
, request_t
* request
, ConnStateData
* conn
, http_hdr_type type
)
850 const char *proxy_auth
;
851 auth_user_hash_pointer
*usernamehash
, *proxy_auth_hash
= NULL
;
852 auth_user_t
*auth_user
;
853 ntlm_request_t
*ntlm_request
;
854 ntlm_user_t
*ntlm_user
;
855 LOCAL_ARRAY(char, ntlmhash
, NTLM_CHALLENGE_SZ
* 2);
857 proxy_auth
= httpHeaderGetStr(&request
->header
, type
);
859 auth_user
= auth_user_request
->auth_user
;
861 assert(auth_user
->auth_type
== AUTH_NTLM
);
862 assert(auth_user
->scheme_data
!= NULL
);
863 assert(auth_user_request
->scheme_data
!= NULL
);
864 ntlm_user
= auth_user
->scheme_data
;
865 ntlm_request
= auth_user_request
->scheme_data
;
866 switch (ntlm_request
->auth_state
) {
867 case AUTHENTICATE_STATE_NONE
:
868 /* we've recieved a negotiate request. pass to a helper */
869 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n",
871 ntlm_request
->auth_state
= AUTHENTICATE_STATE_NEGOTIATE
;
872 ntlm_request
->ntlmnegotiate
= xstrndup(proxy_auth
, NTLM_CHALLENGE_SZ
+ 5);
873 conn
->auth_type
= AUTH_NTLM
;
874 conn
->auth_user_request
= auth_user_request
;
875 /* and lock for the connection duration */
876 debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n");
877 authenticateAuthUserRequestLock(auth_user_request
);
880 case AUTHENTICATE_STATE_NEGOTIATE
:
881 ntlm_request
->auth_state
= AUTHENTICATE_STATE_CHALLENGE
;
884 case AUTHENTICATE_STATE_CHALLENGE
:
885 /* we should have recieved a NTLM challenge. pass it to the same
887 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth
);
888 /* do a cache lookup here. If it matches it's a successful ntlm
889 * challenge - release the helper and use the existing auth_user
891 if (strncmp("NTLM ", proxy_auth
, 5) == 0) {
892 ntlm_request
->ntlmauthenticate
= xstrdup(proxy_auth
);
894 fatal("Incorrect scheme in auth header\n");
895 /* TODO: more fault tolerance.. reset the auth scheme here */
897 /* cache entries have authenticateauthheaderchallengestring */
898 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
899 ntlm_request
->ntlmauthenticate
,
900 ntlm_request
->authchallenge
);
901 /* see if we already know this user's authenticate */
902 debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash
);
903 assert(proxy_auth_cache
!= NULL
);
904 proxy_auth_hash
= hash_lookup(proxy_auth_cache
, ntlmhash
);
905 if (!proxy_auth_hash
) { /* not in the hash table */
906 debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n");
907 ntlm_request
->auth_state
= AUTHENTICATE_STATE_RESPONSE
;
908 /* verify with the ntlm helper */
910 debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n");
911 /* throw away the temporary entry */
912 authenticateNTLMReleasehelper(auth_user_request
);
913 authenticateAuthUserMerge(auth_user
, proxy_auth_hash
->auth_user
);
914 auth_user
= proxy_auth_hash
->auth_user
;
915 auth_user_request
->auth_user
= auth_user
;
916 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
918 debug(29, 9) ("found matching cache entry\n");
919 assert(auth_user
->auth_type
== AUTH_NTLM
);
920 /* get the existing entries details */
921 ntlm_user
= auth_user
->scheme_data
;
922 debug(29, 9) ("Username to be used is %s\n",
923 ntlm_user
->username
);
924 auth_user
->flags
.credentials_ok
= 1; /* authenticated ok */
925 /* on ntlm auth we do not unlock the auth_user until the
926 * connection is dropped. Thank MS for this quirk */
927 auth_user
->expiretime
= current_time
.tv_sec
;
928 auth_user
->ip_expiretime
= squid_curtime
;
932 case AUTHENTICATE_STATE_RESPONSE
:
933 /* auth-challenge pair cache miss. We've just got the response */
934 /*add to cache and let them through */
935 ntlm_request
->auth_state
= AUTHENTICATE_STATE_DONE
;
936 /* this connection is authenticated */
937 debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n",
938 ntlm_request
->authchallenge
,
939 ntlm_request
->ntlmauthenticate
,
940 ntlm_user
->username
);
941 /* cache entries have authenticateauthheaderchallengestring */
942 snprintf(ntlmhash
, sizeof(ntlmhash
) - 1, "%s%s",
943 ntlm_request
->ntlmauthenticate
,
944 ntlm_request
->authchallenge
);
945 /* see if this is an existing user with a different proxy_auth
947 if ((usernamehash
= hash_lookup(proxy_auth_username_cache
,
948 ntlm_user
->username
))) {
949 while ((usernamehash
->auth_user
->auth_type
!=
950 auth_user
->auth_type
) && (usernamehash
->next
) &&
951 !authenticateNTLMcmpUsername(usernamehash
->auth_user
->scheme_data
, ntlm_user
))
952 usernamehash
= usernamehash
->next
;
953 if (usernamehash
->auth_user
->auth_type
== auth_user
->auth_type
) {
955 * add another link from the new proxy_auth to the
956 * auth_user structure and update the information */
957 assert(proxy_auth_hash
== NULL
);
958 authenticateProxyAuthCacheAddLink(ntlmhash
, usernamehash
->auth_user
);
959 /* we can't seamlessly recheck the username due to the
960 * challenge nature of the protocol. Just free the
961 * temporary auth_user */
962 authenticateAuthUserMerge(auth_user
, usernamehash
->auth_user
);
963 auth_user
= usernamehash
->auth_user
;
964 auth_user_request
->auth_user
= auth_user
;
966 conn
->auth_user
= auth_user
;
970 /* store user in hash's */
971 authenticateUserNameCacheAdd(auth_user
);
972 authenticateProxyAuthCacheAddLink(ntlmhash
, auth_user
);
974 /* set these to now because this is either a new login from an
975 * existing user or a new user */
976 auth_user
->expiretime
= current_time
.tv_sec
;
977 auth_user
->ip_expiretime
= squid_curtime
;
978 auth_user
->flags
.credentials_ok
= 1; /*authenticated ok */
981 case AUTHENTICATE_STATE_DONE
:
982 fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n");
983 #if 0 /* done in acl.c */
984 case AUTHENTICATE_STATE_DONE
:
985 debug(28, 5) ("aclMatchProxyAuth: connection in state Done. using connection credentials for the request. \n");
986 /* is it working right? */
987 assert(checklist
->auth_user
== NULL
);
988 assert(checklist
->conn
->auth_user
!= NULL
);
989 /* we have a valid username. */
990 auth_user
= checklist
->conn
->auth_user
;
991 /* store the username in the request for logging */
992 xstrncpy(checklist
->request
->authuser
,
993 auth_user
->auth_data
.ntlm_auth
.username
,
995 if (auth_user
->expiretime
+ Config
.authenticateTTL
> current_time
.tv_sec
997 auth_user
->expiretime
= current_time
.tv_sec
;
999 //user passed externa; authentication in every case to get here. f.
1001 } /* we don't unlock the auth_user until the connection is dropped. Thank
1002 * MS for this quirk. */ if (authenticateCheckAuthUserIP(checklist
->src_addr
, auth_user
)) {
1003 /* Once the match is completed we have finished with the
1004 * auth_user structure */
1005 /* check to see if we have matched the user-acl before */
1006 return aclCacheMatchAcl(&auth_user
->proxy_match_cache
, acltype
,
1007 data
, auth_user
->auth_data
.ntlm_auth
.username
);