]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/ntlm/auth_ntlm.cc
Various auth_rewrite/splay_userauth fixes by Robert Collins
[thirdparty/squid.git] / src / auth / ntlm / auth_ntlm.cc
1
2 /*
3 * $Id: auth_ntlm.cc,v 1.2 2001/01/08 19:36:27 hno Exp $
4 *
5 * DEBUG: section 29 NTLM Authenticator
6 * AUTHOR: Robert Collins
7 *
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
10 *
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.
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
44 static void
45 authenticateStateFree(authenticateStateData * r)
46 {
47 cbdataFree(r);
48 }
49
50 /* NTLM Scheme */
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;
70
71 /* helper callbacks to handle per server state data */
72 static HLPSAVAIL authenticateNTLMHelperServerAvailable;
73 static HLPSONEQ authenticateNTLMHelperServerOnEmpty;
74
75 static statefulhelper *ntlmauthenticators = NULL;
76
77 CBDATA_TYPE(authenticateStateData);
78
79 static int authntlm_initialised = 0;
80
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;
85
86 static hash_table *proxy_auth_cache = NULL;
87
88 /*
89 *
90 * Private Functions
91 *
92 */
93
94 void
95 authNTLMDone(void)
96 {
97 // memPoolDestroy(ufs_state_pool);
98
99 if (ntlmauthenticators)
100 helperStatefulShutdown(ntlmauthenticators);
101 authntlm_initialised = 0;
102 if (!shutting_down)
103 return;
104 helperStatefulFree(ntlmauthenticators);
105 ntlmauthenticators = NULL;
106 memPoolDestroy(ntlm_helper_state_pool);
107 memPoolDestroy(ntlm_request_pool);
108 memPoolDestroy(ntlm_user_pool);
109
110 }
111
112 /* free any allocated configuration details */
113 void
114 authNTLMFreeConfig(authScheme * scheme)
115 {
116 if (ntlmConfig == NULL)
117 return;
118 assert(ntlmConfig == scheme->scheme_data);
119 if (ntlmConfig->authenticate)
120 wordlistDestroy(&ntlmConfig->authenticate);
121 xfree(ntlmConfig);
122 ntlmConfig = NULL;
123 }
124
125 static void
126 authNTLMCfgDump(StoreEntry * entry, const char *name, authScheme * scheme)
127 {
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);
133 list = list->next;
134 }
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);
139
140 }
141
142 static void
143 authNTLMParse(authScheme * scheme, int n_configured, char *param_str)
144 {
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;
154 }
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);
165 } else {
166 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str);
167 }
168 }
169
170
171 void
172 authSchemeSetup_ntlm(authscheme_entry_t * authscheme)
173 {
174 #if 0
175 static int ntlminit = 0;
176 #endif
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;
195 }
196
197 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
198 * config file */
199 static void
200 authNTLMInit(authScheme * scheme)
201 {
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));
206 if (!ntlm_user_pool)
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? */
225 if (!ntlminit) {
226 cachemgrRegister("ntlmauthenticator",
227 "User NTLM Authenticator Stats",
228 authenticateNTLMStats, 0, 1);
229 ntlminit++;
230 }
231 CBDATA_INIT_TYPE(authenticateStateData);
232 }
233 }
234
235 int
236 authenticateNTLMActive()
237 {
238 if ((ntlmConfig != NULL) && (ntlmConfig->authenticate != NULL) &&
239 (ntlmConfig->authenticateChildren != 0) && (ntlmConfig->challengeuses > -1)
240 && (ntlmConfig->challengelifetime>-1))
241 return 1;
242 return 0;
243 }
244
245 /* NTLM Scheme */
246
247 int
248 authenticateNTLMDirection(auth_user_request_t * auth_user_request)
249 {
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");
255 return -2;
256 case AUTHENTICATE_STATE_NEGOTIATE: /* send to helper */
257 case AUTHENTICATE_STATE_RESPONSE: /*send to helper */
258 return -1;
259 case AUTHENTICATE_STATE_CHALLENGE: /* send to client */
260 return 1;
261 case AUTHENTICATE_STATE_DONE: /* do nothing.. */
262 return 0;
263 }
264 return -2;
265 }
266
267 /*
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
270 * else.
271 */
272 void
273 authenticateNTLMFixErrorHeader(auth_user_request_t * auth_user_request, HttpReply * rep, http_hdr_type type, request_t * request)
274 {
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;
286 } else {
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;
297 break;
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);
303 break;
304 default:
305 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request->auth_state);
306 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
307 }
308 }
309 }
310 }
311
312 void
313 authNTLMRequestFree(ntlm_request_t * ntlm_request)
314 {
315 if (!ntlm_request)
316 return;
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);
324 }
325
326 void
327 authNTLMAURequestFree(auth_user_request_t * auth_user_request)
328 {
329 if (auth_user_request->scheme_data)
330 authNTLMRequestFree((ntlm_request_t *) auth_user_request->scheme_data);
331 auth_user_request->scheme_data = NULL;
332 }
333
334 void
335 authenticateNTLMFreeUser(auth_user_t * auth_user)
336 {
337 dlink_node *link, *tmplink;
338 ntlm_user_t *ntlm_user = auth_user->scheme_data;
339 auth_user_hash_pointer *proxy_auth_hash;
340
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;
346 while (link) {
347 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%d'\n", link->data);
348 proxy_auth_hash = link->data;
349 tmplink = link;
350 link = link->next;
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);
356 }
357 memPoolFree(ntlm_user_pool, ntlm_user);
358 auth_user->scheme_data = NULL;
359 }
360
361 static stateful_helper_callback_t
362 authenticateNTLMHandleplaceholder(void *data, void *lastserver, char *reply)
363 {
364 authenticateStateData *r = data;
365 stateful_helper_callback_t result = S_HELPER_UNKNOWN;
366 int valid;
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);
377 return result;
378 }
379
380 static stateful_helper_callback_t
381 authenticateNTLMHandleReply(void *data, void *lastserver, char *reply)
382 {
383 #if 0
384 authenticateStatefulStateData *r = data;
385 #endif
386 authenticateStateData *r = data;
387 ntlm_helper_state_t *helperstate;
388 int valid;
389 stateful_helper_callback_t result = S_HELPER_UNKNOWN;
390 #if 0
391 void *nextserver = NULL;
392 #endif
393 char *t = 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);
401 if (valid) {
402 if (reply) {
403 /* seperate out the useful data */
404 if (strncasecmp(reply, "TT ", 3) == 0) {
405 reply += 3;
406 /* we have been given a Challenge */
407 /* we should check we weren't given an empty challenge */
408 #if 0
409 result = S_HELPER_RESERVE;
410 #endif
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;
426 #if 0
427 nextserver = lastserver;
428 #endif
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 */
436 reply += 3;
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 */
468 *t = '\0';
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
474 * Needing YR. */
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
484 * this helper */
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
489 * resets */
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);
497 } else {
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 */
504 *t = '\0';
505 /* now we mark the helper for resetting. */
506 helperstate->starve = 1;
507 }
508 ntlm_request->auth_state = AUTHENTICATE_STATE_NONE;
509 } else {
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;
524 }
525 } else {
526 fatal("authenticateNTLMHandleReply: called with no result string\n");
527 }
528 r->handler(r->data, NULL);
529 } else {
530 debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%d'.\n", lastserver);
531 result = S_HELPER_RELEASE;
532 }
533 authenticateStateFree(r);
534 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result);
535 return result;
536 }
537
538 #if 0
539 static void
540 authenticateNTLMStateFree(authenticateNTLMStateData * r)
541 {
542 cbdataFree(r);
543 }
544
545 #endif
546
547 static void
548 authenticateNTLMStats(StoreEntry * sentry)
549 {
550 storeAppendPrintf(sentry, "NTLM Authenticator Statistics:\n");
551 helperStatefulStats(sentry, ntlmauthenticators);
552 }
553
554 /* is a particular challenge still valid ? */
555 int
556 authenticateNTLMValidChallenge(ntlm_helper_state_t * helperstate)
557 {
558 if (helperstate->challenge == NULL)
559 return 0;
560 return 1;
561 }
562
563 /* does our policy call for changing the challenge now? */
564 int
565 authenticateNTLMChangeChallenge(ntlm_helper_state_t * helperstate)
566 {
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)
571 return 1;
572 if (helperstate->renewed + ntlmConfig->challengelifetime >= squid_curtime)
573 return 1;
574 return 0;
575 }
576
577 /* send the initial data to a stateful ntlm authenticator module */
578 static void
579 authenticateNTLMStart(auth_user_request_t * auth_user_request, RH * handler, void *data)
580 {
581 #if 0
582 authenticateStatefulStateData *r = NULL;
583 #endif
584 authenticateStateData *r = NULL;
585 helper_stateful_server *server;
586 ntlm_helper_state_t *helperstate;
587 char buf[8192];
588 char *sent_string = NULL;
589 ntlm_user_t *ntlm_user;
590 ntlm_request_t *ntlm_request;
591 auth_user_t *auth_user;
592
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;
597 assert(ntlm_user);
598 assert(ntlm_request);
599 assert(handler);
600 assert(data);
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);
606 break;
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);
611 break;
612 default:
613 fatal("Invalid authenticate state for NTLMStart");
614 }
615
616 while (!xisspace(*sent_string)) /*trim NTLM */
617 sent_string++;
618
619 while (xisspace(*sent_string)) /*trim leading spaces */
620 sent_string++;
621
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);
627 handler(data, NULL);
628 return;
629 }
630 #ifdef NTLMHELPPROTOCOLV2
631 r = CBDATA_ALLOC(authenticateStateData, NULL);
632 r->handler = handler;
633 cbdataLock(data);
634 r->data = data;
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");
639 #else
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:
644 /*
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
648 */
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);
658 if (server != NULL)
659 helperstate = helperStatefulServerGetData(server);
660 }
661 if (server == NULL)
662 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
663
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;
671 cbdataLock(data);
672 r->data = data;
673 r->auth_user_request = auth_user_request;
674 if (server == NULL) {
675 helperStatefulSubmit(ntlmauthenticators, NULL, authenticateNTLMHandleplaceholder, r, ntlm_request->authhelper);
676 } else {
677 snprintf(buf, 8192, "YR\n");
678 helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authhelper);
679 }
680 } else {
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);
688 handler(data, NULL);
689 }
690
691 break;
692 case AUTHENTICATE_STATE_RESPONSE:
693 r = CBDATA_ALLOC(authenticateStateData, NULL);
694 r->handler = handler;
695 cbdataLock(data);
696 r->data = data;
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");
701 break;
702 default:
703 fatal("Invalid authenticate state for NTLMStart");
704 }
705 #endif
706 }
707
708 /* callback used by stateful helper routines */
709 int
710 authenticateNTLMHelperServerAvailable(void *data)
711 {
712 ntlm_helper_state_t *statedata = data;
713 if (statedata != NULL) {
714 if (statedata->starve) {
715 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
716 return 0;
717 } else {
718 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
719 return 1;
720 }
721 }
722 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
723 return 0;
724 }
725
726 void
727 authenticateNTLMHelperServerOnEmpty(void *data)
728 {
729 ntlm_helper_state_t *statedata = data;
730 if (statedata == NULL)
731 return;
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;
740 }
741 }
742
743
744 /* clear the NTLM helper of being reserved for future requests */
745 void
746 authenticateNTLMReleasehelper(auth_user_request_t * auth_user_request)
747 {
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;
755 }
756
757 /* clear any connection related authentication details */
758 void
759 authenticateNTLMOnCloseConnection(ConnStateData * conn)
760 {
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;
772 }
773 }
774
775 /* authenticateUserUsername: return a pointer to the username in the */
776 char *
777 authenticateNTLMUsername(auth_user_t * auth_user)
778 {
779 ntlm_user_t *ntlm_user = auth_user->scheme_data;
780 if (ntlm_user)
781 return ntlm_user->username;
782 return NULL;
783 }
784
785
786 /*
787 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
788 * Auth_user structure.
789 */
790
791 void
792 authenticateDecodeNTLMAuth(auth_user_request_t * auth_user_request, const char *proxy_auth)
793 {
794 dlink_node *node;
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);
804
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");
807 return;
808 }
809
810 int
811 authenticateNTLMcmpUsername(ntlm_user_t * u1, ntlm_user_t * u2)
812 {
813 return strcmp(u1->username, u2->username);
814 }
815
816 void
817 authenticateProxyAuthCacheAddLink(const char *key, auth_user_t * auth_user)
818 {
819 auth_user_hash_pointer *proxy_auth_hash;
820 ntlm_user_t *ntlm_user;
821 proxy_auth_hash =
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);
829 }
830
831
832 int
833 authNTLMAuthenticated(auth_user_request_t * auth_user_request)
834 {
835 ntlm_request_t *ntlm_request = auth_user_request->scheme_data;
836 if (ntlm_request->auth_state == AUTHENTICATE_STATE_DONE)
837 return 1;
838 debug(29, 9) ("User not fully authenticated.\n");
839 return 0;
840 }
841
842 #if 0
843 static acl_proxy_auth_user *
844 authenticateNTLMAuthenticateUser(void *data, const char *proxy_auth, ConnStateData * conn)
845 #else
846 static void
847 authenticateNTLMAuthenticateUser(auth_user_request_t * auth_user_request, request_t * request, ConnStateData * conn, http_hdr_type type)
848 #endif
849 {
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);
856 /* get header */
857 proxy_auth = httpHeaderGetStr(&request->header, type);
858
859 auth_user = auth_user_request->auth_user;
860 assert(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",
870 proxy_auth);
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);
878 return;
879 break;
880 case AUTHENTICATE_STATE_NEGOTIATE:
881 ntlm_request->auth_state = AUTHENTICATE_STATE_CHALLENGE;
882 return;
883 break;
884 case AUTHENTICATE_STATE_CHALLENGE:
885 /* we should have recieved a NTLM challenge. pass it to the same
886 * helper process */
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
890 * details. */
891 if (strncmp("NTLM ", proxy_auth, 5) == 0) {
892 ntlm_request->ntlmauthenticate = xstrdup(proxy_auth);
893 } else {
894 fatal("Incorrect scheme in auth header\n");
895 /* TODO: more fault tolerance.. reset the auth scheme here */
896 }
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 */
909 } else {
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;
917 /* we found one */
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;
929 }
930 return;
931 break;
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
946 * string */
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) {
954 /*
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;
965 #if 0
966 conn->auth_user = auth_user;
967 #endif
968 }
969 } else {
970 /* store user in hash's */
971 authenticateUserNameCacheAdd(auth_user);
972 authenticateProxyAuthCacheAddLink(ntlmhash, auth_user);
973 }
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 */
979 return;
980 break;
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,
994 USER_IDENT_SZ);
995 if (auth_user->expiretime + Config.authenticateTTL > current_time.tv_sec
996 ) {
997 auth_user->expiretime = current_time.tv_sec;
998 } else {
999 //user passed externa; authentication in every case to get here. f.
1000 Let it through
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);
1008 } else {
1009 return 0;
1010 }
1011 break;
1012 #endif
1013 }
1014
1015 return;
1016 }