]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/ntlm/auth_ntlm.cc
Merge in second phase Disk IO refactoring, separating out the store and the io logic.
[thirdparty/squid.git] / src / auth / ntlm / auth_ntlm.cc
1
2 /*
3 * $Id: auth_ntlm.cc,v 1.44 2004/12/20 16:30:40 robertc Exp $
4 *
5 * DEBUG: section 29 NTLM Authenticator
6 * AUTHOR: Robert Collins
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
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.
19 *
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.
24 *
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.
29 *
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.
33 *
34 */
35
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 */
39
40
41 #include "squid.h"
42 #include "auth_ntlm.h"
43 #include "authenticate.h"
44 #include "Store.h"
45 #include "client_side.h"
46 #include "HttpReply.h"
47 #include "HttpRequest.h"
48 /* TODO remove this include */
49 #include "ntlmScheme.h"
50
51 static void
52 authenticateStateFree(authenticateStateData * r)
53 {
54 cbdataFree(r);
55 }
56
57 /* NTLM Scheme */
58 static HLPSCB authenticateNTLMHandleReply;
59 static HLPSCB authenticateNTLMHandleplaceholder;
60 static AUTHSSTATS authenticateNTLMStats;
61
62 /* helper callbacks to handle per server state data */
63 static HLPSAVAIL authenticateNTLMHelperServerAvailable;
64 static HLPSONEQ authenticateNTLMHelperServerOnEmpty;
65
66 static statefulhelper *ntlmauthenticators = NULL;
67
68 CBDATA_TYPE(authenticateStateData);
69
70 static int authntlm_initialised = 0;
71
72 static MemAllocatorProxy *ntlm_helper_state_pool = NULL;
73 static MemAllocatorProxy *ntlm_user_hash_pool = NULL;
74
75 static auth_ntlm_config ntlmConfig;
76
77 static hash_table *proxy_auth_cache = NULL;
78
79 /*
80 *
81 * Private Functions
82 *
83 */
84
85 /* move to ntlmScheme.cc */
86 void
87 ntlmScheme::done()
88 {
89 /* TODO: this should be a Config call. */
90 debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n");
91
92 if (ntlmauthenticators)
93 helperStatefulShutdown(ntlmauthenticators);
94
95 authntlm_initialised = 0;
96
97 if (!shutting_down)
98 return;
99
100 if (ntlmauthenticators)
101 helperStatefulFree(ntlmauthenticators);
102
103 ntlmauthenticators = NULL;
104
105 #if DEBUGSHUTDOWN
106
107 if (ntlm_helper_state_pool) {
108 delete ntlm_helper_state_pool;
109 ntlm_helper_state_pool = NULL;
110 }
111
112 /* Removed for some reason..
113 if (ntlm_user_pool) {
114 delete ntlm_user_pool;ntlm_user_pool = NULL;
115 }
116 */
117
118 #endif
119 debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n");
120 }
121
122 /* free any allocated configuration details */
123 void
124 AuthNTLMConfig::done()
125 {
126 if (authenticate)
127 wordlistDestroy(&authenticate);
128 }
129
130 void
131 AuthNTLMConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme)
132 {
133 wordlist *list = authenticate;
134 storeAppendPrintf(entry, "%s %s", name, "ntlm");
135
136 while (list != NULL) {
137 storeAppendPrintf(entry, " %s", list->key);
138 list = list->next;
139 }
140
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);
145
146 }
147
148 AuthNTLMConfig::AuthNTLMConfig()
149 {
150 /* TODO Move into initialisation list */
151 authenticateChildren = 5;
152 challengeuses = 0;
153 challengelifetime = 60;
154 }
155
156 void
157 AuthNTLMConfig::parse(AuthConfig * scheme, int n_configured, char *param_str)
158 {
159 if (strcasecmp(param_str, "program") == 0) {
160 if (authenticate)
161 wordlistDestroy(&authenticate);
162
163 parse_wordlist(&authenticate);
164
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);
172 } else {
173 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str);
174 }
175
176 /*
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.
183 */
184 if (authenticate)
185 Config.onoff.pipeline_prefetch = 0;
186 }
187
188 const char *
189 AuthNTLMConfig::type() const
190 {
191 return ntlmScheme::GetInstance().type();
192 }
193
194 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
195 * config file */
196 void
197 AuthNTLMConfig::init(AuthConfig * scheme)
198 {
199 static int ntlminit = 0;
200
201 if (authenticate) {
202 if (!ntlm_helper_state_pool)
203 ntlm_helper_state_pool = new MemAllocatorProxy("NTLM Helper State data", sizeof(ntlm_helper_state_t));
204
205 if (!ntlm_user_hash_pool)
206
207 ntlm_user_hash_pool = new MemAllocatorProxy("NTLM Header Hash Data", sizeof(struct ProxyAuthCachePointer));
208
209 authntlm_initialised = 1;
210
211 if (ntlmauthenticators == NULL)
212 ntlmauthenticators = helperStatefulCreate("ntlmauthenticator");
213
214 if (!proxy_auth_cache)
215 proxy_auth_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
216
217 assert(proxy_auth_cache);
218
219 ntlmauthenticators->cmdline = authenticate;
220
221 ntlmauthenticators->n_to_start = authenticateChildren;
222
223 ntlmauthenticators->ipc_type = IPC_STREAM;
224
225 ntlmauthenticators->datapool = ntlm_helper_state_pool;
226
227 ntlmauthenticators->IsAvailable = authenticateNTLMHelperServerAvailable;
228
229 ntlmauthenticators->OnEmptyQueue = authenticateNTLMHelperServerOnEmpty;
230
231 helperStatefulOpenServers(ntlmauthenticators);
232
233 /*
234 * TODO: In here send the initial YR to preinitialise the
235 * challenge cache
236 */
237 /*
238 * Think about this... currently we ask when the challenge
239 * is needed. Better?
240 */
241 if (!ntlminit) {
242 cachemgrRegister("ntlmauthenticator",
243 "NTLM User Authenticator Stats",
244 authenticateNTLMStats, 0, 1);
245 ntlminit++;
246 }
247
248 CBDATA_INIT_TYPE(authenticateStateData);
249 }
250 }
251
252 bool
253 AuthNTLMConfig::active() const
254 {
255 return authntlm_initialised == 1;
256 }
257
258 bool
259 AuthNTLMConfig::configured() const
260 {
261 if ((authenticate != NULL) && (authenticateChildren != 0) && (challengeuses > -1) && (challengelifetime > -1)) {
262 debug(29, 9) ("authNTLMConfigured: returning configured\n");
263 return true;
264 }
265
266 debug(29, 9) ("authNTLMConfigured: returning unconfigured\n");
267 return false;
268 }
269
270 /* NTLM Scheme */
271 int
272 AuthNTLMUserRequest::module_direction()
273 {
274 /* null auth_user is checked for by authenticateDirection */
275
276 switch (auth_state) {
277
278 /* no progress at all. */
279
280 case AUTHENTICATE_STATE_NONE:
281 debug(29, 1) ("AuthNTLMUserRequest::direction: called before NTLM Authenticate!. Report a bug to squid-dev.\n");
282 /* fall thru */
283
284 case AUTHENTICATE_STATE_FAILED:
285 return -2;
286
287 /* send to helper */
288
289 case AUTHENTICATE_STATE_NEGOTIATE:
290
291 /*send to helper */
292
293 case AUTHENTICATE_STATE_RESPONSE:
294 return -1;
295
296 /* send to client */
297
298 case AUTHENTICATE_STATE_CHALLENGE:
299 return 1;
300
301 /* do nothing.. */
302
303 case AUTHENTICATE_STATE_DONE:
304 return 0;
305 }
306
307 return -2;
308 }
309
310 /*
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
313 * else.
314 */
315 void
316 AuthNTLMConfig::fixHeader(auth_user_request_t *auth_user_request, HttpReply *rep, http_hdr_type type, HttpRequest * request)
317 {
318 AuthNTLMUserRequest *ntlm_request;
319
320 if (authenticate) {
321 /* New request, no user details */
322
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;
331 } else {
332 ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request);
333 assert (ntlm_request);
334
335 switch (ntlm_request->auth_state) {
336
337 case AUTHENTICATE_STATE_NONE:
338
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;
347 break;
348
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);
354 break;
355
356 default:
357 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request->auth_state);
358 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
359 }
360 }
361 }
362 }
363
364 NTLMUser::~NTLMUser()
365 {
366 dlink_node *link, *tmplink;
367 ProxyAuthCachePointer *proxy_auth_hash;
368 debug(29, 5) ("NTLMUser::~NTLMUser: Clearing NTLM scheme data\n");
369
370 /* were they linked in by one or more proxy-authenticate headers */
371 link = proxy_auth_list.head;
372
373 while (link) {
374 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%p'\n", link->data);
375 proxy_auth_hash = static_cast<ProxyAuthCachePointer *>(link->data);
376 tmplink = link;
377 link = link->next;
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);
383 }
384
385 }
386
387 static stateful_helper_callback_t
388 authenticateNTLMHandleplaceholder(void *data, void *lastserver, char *reply)
389 {
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 */
396
397 if (!cbdataReferenceValid(r->data)) {
398 debug(29, 1) ("AuthenticateNTLMHandlePlacheholder: invalid callback data.\n");
399 return result;
400 }
401
402 /* call authenticateNTLMStart to retry this request */
403 debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n");
404
405 r->auth_user_request->start(r->handler, r->data);
406
407 cbdataReferenceDone(r->data);
408
409 authenticateStateFree(r);
410
411 return result;
412 }
413
414 static stateful_helper_callback_t
415 authenticateNTLMHandleReply(void *data, void *lastserver, char *reply)
416 {
417 authenticateStateData *r = static_cast<authenticateStateData *>(data);
418 ntlm_helper_state_t *helperstate;
419 stateful_helper_callback_t result = S_HELPER_UNKNOWN;
420 char *t = NULL;
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>");
426
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;
433 }
434
435 if (!reply) {
436 /*
437 * TODO: this occurs when a helper crashes. We should clean
438 * up that helpers resources and queued requests.
439 */
440 fatal("authenticateNTLMHandleReply: called with no result string\n");
441 }
442
443 /* seperate out the useful data */
444 if (strncasecmp(reply, "TT ", 3) == 0) {
445 reply += 3;
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)));
450
451 if (helperstate == NULL)
452 fatal("lost NTLM helper state! quitting\n");
453
454 helperstate->challenge = xstrdup(reply);
455
456 helperstate->challengeuses = 0;
457
458 helperstate->renewed = squid_curtime;
459
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);
463
464 assert(r->auth_user_request->user()->auth_type == AUTH_NTLM);
465
466 auth_user_request = r->auth_user_request;
467
468 ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request);
469
470 assert(ntlm_request != NULL);
471
472 result = S_HELPER_DEFER;
473
474 /* reserve the server for future authentication */
475 ntlm_request->authserver_deferred = 1;
476
477 debug(29, 9) ("authenticateNTLMHandleReply: helper '%p'\n", lastserver);
478
479 assert(ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE);
480
481 ntlm_request->authserver = static_cast<helper_stateful_server *>(lastserver);
482
483 ntlm_request->authchallenge = xstrdup(reply);
484 } else if (strncasecmp(reply, "AF ", 3) == 0) {
485 /* we're finished, release the helper */
486 reply += 3;
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
501
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
510 * case */
511 /* AF code: mark user as authenticated */
512 reply += 3;
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;
530 #endif
531
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;
547
548 if ((t = strchr(reply, ' '))) /* strip after a space */
549 *t = '\0';
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
558 * Needing YR. */
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
568 * this helper */
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;
573
574 if (ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE) {
575 /* The helper broke on YR. It automatically
576 * resets */
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);
587 return result;
588 }
589
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);
593
594 if ((t = strchr(reply, ' '))) /* strip after a space */
595 *t = '\0';
596
597 /* now we mark the helper for resetting. */
598 helperstate->starve = 1;
599
600 ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
601 } else {
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;
618 }
619
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);
624 return result;
625 }
626
627 static void
628 authenticateNTLMStats(StoreEntry * sentry)
629 {
630 storeAppendPrintf(sentry, "NTLM Authenticator Statistics:\n");
631 helperStatefulStats(sentry, ntlmauthenticators);
632 }
633
634 /* is a particular challenge still valid ? */
635 static int
636 authenticateNTLMValidChallenge(ntlm_helper_state_t * helperstate)
637 {
638 debug(29, 9) ("authenticateNTLMValidChallenge: Challenge is %s\n", helperstate->challenge ? "Valid" : "Invalid");
639
640 if (helperstate->challenge == NULL)
641 return 0;
642
643 return 1;
644 }
645
646 /* does our policy call for changing the challenge now? */
647 static int
648 authenticateNTLMChangeChallenge_p(ntlm_helper_state_t * helperstate)
649 {
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 */
653
654 if (!helperstate->renewed) {
655 /* first use, no challenge has been set. Without this check, it will
656 * loop forever */
657 debug(29, 5) ("authenticateNTLMChangeChallenge_p: first use\n");
658 return 0;
659 }
660
661 if (helperstate->challengeuses > ntlmConfig.challengeuses) {
662 debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge uses (%d) exceeded max uses (%d)\n", helperstate->challengeuses, ntlmConfig.challengeuses);
663 return 1;
664 }
665
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)));
668 return 1;
669 }
670
671 debug(29, 9) ("Challenge is to be reused\n");
672 return 0;
673 }
674
675 /* send the initial data to a stateful ntlm authenticator module */
676 void
677 AuthNTLMUserRequest::module_start(RH * handler, void *data)
678 {
679 authenticateStateData *r = NULL;
680 helper_stateful_server *server;
681 ntlm_helper_state_t *helperstate;
682 char buf[8192];
683 char *sent_string = NULL;
684 ntlm_user_t *ntlm_user;
685 auth_user_t *auth_user;
686
687 auth_user = this->user();
688 ntlm_user = dynamic_cast<ntlm_user_t *>(auth_user);
689 assert(ntlm_user);
690 assert(data);
691 assert(auth_user->auth_type == AUTH_NTLM);
692 debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", auth_state);
693
694 switch (auth_state) {
695
696 case AUTHENTICATE_STATE_NEGOTIATE:
697 sent_string = ntlmnegotiate;
698 break;
699
700 case AUTHENTICATE_STATE_RESPONSE:
701 sent_string = ntlmauthenticate;
702 assert(authserver);
703 debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%p'.\n", authserver);
704 break;
705
706 default:
707 fatal("Invalid authenticate state for NTLMStart");
708 }
709
710 while (!xisspace(*sent_string)) /*trim NTLM */
711 sent_string++;
712
713 while (xisspace(*sent_string)) /*trim leading spaces */
714 sent_string++;
715
716 debug(29, 9) ("authenticateNTLMStart: state '%d'\n", auth_state);
717
718 debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string);
719
720 if (ntlmConfig.authenticate == NULL) {
721 debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string);
722 handler(data, NULL);
723 return;
724 }
725
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) {
729
730 case AUTHENTICATE_STATE_NEGOTIATE:
731 /*
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
735 */
736 server = helperStatefulDefer(ntlmauthenticators);
737 helperstate = server ? static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(server)) : NULL;
738
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;
747 }
748
749 if (server == NULL)
750 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
751
752 authserver = server;
753
754 /* tell the log what helper we have been given */
755 debug(29, 9) ("authenticateNTLMStart: helper '%p' assigned\n", server);
756
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;
764
765 if (server == NULL) {
766 helperStatefulSubmit(ntlmauthenticators, NULL, authenticateNTLMHandleplaceholder, r, NULL);
767 } else {
768 /* Server with invalid challenge */
769 snprintf(buf, 8192, "YR\n");
770 helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, authserver);
771 }
772 } else {
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
781 */
782 authserver_deferred = 1;
783 handler(data, NULL);
784 }
785
786 break;
787
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");
798 break;
799
800 default:
801 fatal("Invalid authenticate state for NTLMStart");
802 }
803 }
804
805 /* callback used by stateful helper routines */
806 static int
807 authenticateNTLMHelperServerAvailable(void *data)
808 {
809 ntlm_helper_state_t *statedata = static_cast<ntlm_helper_state_t *>(data);
810
811 if (statedata != NULL) {
812 if (statedata->starve) {
813 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
814 return 0;
815 } else {
816 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
817 return 1;
818 }
819 }
820
821 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
822 return 0;
823 }
824
825 static void
826 authenticateNTLMHelperServerOnEmpty(void *data)
827 {
828 ntlm_helper_state_t *statedata = static_cast<ntlm_helper_state_t *>(data);
829
830 if (statedata == NULL)
831 return;
832
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;
841 }
842 }
843
844
845 /* clear the NTLM helper of being reserved for future requests */
846 static void
847 authenticateNTLMReleaseServer(auth_user_request_t * auth_user_request)
848 {
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;
856 }
857
858 /* clear any connection related authentication details */
859 void
860 AuthNTLMUserRequest::onConnectionClose(ConnStateData *conn)
861 {
862 assert(conn != NULL);
863
864 if (conn->auth_user_request != NULL) {
865 assert (conn->auth_user_request == this);
866 assert(this->conn == conn);
867
868 if (authserver != NULL && authserver_deferred)
869 authenticateNTLMReleaseServer(this);
870
871 /* unlock the connection based lock */
872 debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n");
873
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.
877 */
878 this->unlock();
879
880 conn->auth_user_request = NULL;
881 }
882 }
883
884 /* NTLMLastHeader: return a pointer to the last header used in authenticating
885 * the request/conneciton
886 */
887 const char *
888 AuthNTLMUserRequest::connLastHeader()
889 {
890 return ntlmauthenticate;
891 }
892
893 /*
894 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
895 * Auth_user structure.
896 */
897 AuthUserRequest *
898 AuthNTLMConfig::decode(char const *proxy_auth)
899 {
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);
906
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;
910 }
911
912 static int
913 authenticateNTLMcmpUsername(ntlm_user_t * u1, ntlm_user_t * u2)
914 {
915 return strcmp(u1->username(), u2->username());
916 }
917
918
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
922 */
923 static void
924 authenticateProxyAuthCacheAddLink(const char *key, auth_user_t * auth_user)
925 {
926
927 struct ProxyAuthCachePointer *proxy_auth_hash;
928 dlink_node *node;
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 */
933
934 while (node) {
935
936 if (!strcmp(key, (char const *)((struct ProxyAuthCachePointer *) node->data)->key))
937 return;
938
939 node = node->next;
940 }
941
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);
947 }
948
949 int
950 AuthNTLMUserRequest::authenticated() const
951 {
952 if (auth_state == AUTHENTICATE_STATE_DONE)
953 return 1;
954
955 debug(29, 9) ("User not fully authenticated.\n");
956
957 return 0;
958 }
959
960 void
961 AuthNTLMUserRequest::authenticate(HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type)
962 {
963 const char *proxy_auth;
964
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);
972 /* get header */
973 proxy_auth = httpHeaderGetStr(&request->header, type);
974
975 auth_user = user();
976 assert(auth_user);
977 assert(auth_user->auth_type == AUTH_NTLM);
978 ntlm_user = dynamic_cast<ntlm_user_t *>(auth_user);
979 ntlm_request = this;
980 assert (ntlm_request);
981 /* Check that we are in the client side, where we can generate
982 * auth challenges */
983
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");
987 return;
988 }
989
990 switch (ntlm_request->auth_state) {
991
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");
1002
1003 this->lock()
1004
1005 ;
1006 return;
1007
1008 break;
1009
1010 case AUTHENTICATE_STATE_NEGOTIATE:
1011 ntlm_request->auth_state = AUTHENTICATE_STATE_CHALLENGE;
1012
1013 /* We _MUST_ have the auth challenge by now */
1014 assert(ntlm_request->authchallenge);
1015
1016 return;
1017
1018 break;
1019
1020 case AUTHENTICATE_STATE_CHALLENGE:
1021 /* we should have recieved a NTLM challenge. pass it to the same
1022 * helper process */
1023 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth);
1024
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
1027 * details. */
1028
1029 if (strncmp("NTLM ", proxy_auth, 5) == 0) {
1030 ntlm_request->ntlmauthenticate = xstrdup(proxy_auth);
1031 } else {
1032 fatal("Incorrect scheme in auth header\n");
1033 /* TODO: more fault tolerance.. reset the auth scheme here */
1034 }
1035
1036 /* cache entries have authenticateauthheaderchallengestring */
1037 snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s",
1038 ntlm_request->ntlmauthenticate,
1039 ntlm_request->authchallenge);
1040
1041 /* see if we already know this user's authenticate */
1042 debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash);
1043
1044 assert(proxy_auth_cache != NULL);
1045
1046 proxy_auth_hash = static_cast<ProxyAuthCachePointer *>(hash_lookup(proxy_auth_cache, ntlmhash));
1047
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 */
1052 } else {
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;
1061 /* we found one */
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;
1070 }
1071
1072 return;
1073 break;
1074
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
1089 * string */
1090
1091 if ((usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, ntlm_user->username())))
1092 ) {
1093 while ((usernamehash->user()->auth_type != auth_user->auth_type) && (usernamehash->next) && !authenticateNTLMcmpUsername(dynamic_cast<ntlm_user_t *>(usernamehash->user()), ntlm_user)
1094 )
1095 usernamehash = static_cast<AuthUserHashPointer*>(usernamehash->next);
1096 if (usernamehash->user()->auth_type == auth_user->auth_type) {
1097 /*
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);
1108 }
1109 } else {
1110 /* store user in hash's */
1111 auth_user->addToNameCache();
1112 authenticateProxyAuthCacheAddLink(ntlmhash, auth_user);
1113 }
1114
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;
1118 return;
1119 break;
1120
1121 case AUTHENTICATE_STATE_DONE:
1122 fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n");
1123 break;
1124
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);
1128 return;
1129 }
1130
1131 return;
1132 }
1133
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)
1137 {}
1138
1139 AuthNTLMUserRequest::~AuthNTLMUserRequest()
1140 {
1141 if (ntlmnegotiate)
1142 xfree(ntlmnegotiate);
1143
1144 if (authchallenge)
1145 xfree(authchallenge);
1146
1147 if (ntlmauthenticate)
1148 xfree(ntlmauthenticate);
1149
1150 if (authserver != NULL && authserver_deferred) {
1151 debug(29, 9) ("authenticateNTLMRequestFree: releasing server '%p'\n", authserver);
1152 helperStatefulReleaseServer(authserver);
1153 authserver = NULL;
1154 }
1155 }
1156
1157 void
1158 NTLMUser::deleteSelf() const
1159 {
1160 delete this;
1161 }
1162
1163 NTLMUser::NTLMUser (AuthConfig *config) : AuthUser (config)
1164 {
1165 proxy_auth_list.head = proxy_auth_list.tail = NULL;
1166 }
1167
1168 AuthConfig *
1169 ntlmScheme::createConfig()
1170 {
1171 return &ntlmConfig;
1172 }
1173