]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/ntlm/auth_ntlm.cc
auth_rewrite merge:
[thirdparty/squid.git] / src / auth / ntlm / auth_ntlm.cc
1
2 /*
3 * $Id: auth_ntlm.cc,v 1.4 2001/01/11 00:01:54 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 if (ntlmauthenticators)
98 helperStatefulShutdown(ntlmauthenticators);
99 authntlm_initialised = 0;
100 if (!shutting_down)
101 return;
102 if (ntlmauthenticators)
103 helperStatefulFree(ntlmauthenticators);
104 ntlmauthenticators = NULL;
105 if (ntlm_helper_state_pool) {
106 assert(memPoolInUseCount(ntlm_helper_state_pool) == 0);
107 memPoolDestroy(ntlm_helper_state_pool);
108 ntlm_helper_state_pool = NULL;
109 }
110 if (ntlm_request_pool) {
111 assert(memPoolInUseCount(ntlm_request_pool) == 0);
112 memPoolDestroy(ntlm_request_pool);
113 ntlm_request_pool = NULL;
114 }
115 if (ntlm_user_pool) {
116 assert(memPoolInUseCount(ntlm_user_pool) == 0);
117 memPoolDestroy(ntlm_user_pool);
118 ntlm_user_pool = NULL;
119 }
120 debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n");
121 }
122
123 /* free any allocated configuration details */
124 void
125 authNTLMFreeConfig(authScheme * scheme)
126 {
127 if (ntlmConfig == NULL)
128 return;
129 assert(ntlmConfig == scheme->scheme_data);
130 if (ntlmConfig->authenticate)
131 wordlistDestroy(&ntlmConfig->authenticate);
132 xfree(ntlmConfig);
133 ntlmConfig = NULL;
134 }
135
136 static void
137 authNTLMCfgDump(StoreEntry * entry, const char *name, authScheme * scheme)
138 {
139 auth_ntlm_config *config = scheme->scheme_data;
140 wordlist *list = config->authenticate;
141 storeAppendPrintf(entry, "%s %s", name, "ntlm");
142 while (list != NULL) {
143 storeAppendPrintf(entry, " %s", list->key);
144 list = list->next;
145 }
146 storeAppendPrintf(entry, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n",
147 name, "ntlm", config->authenticateChildren,
148 name, "ntlm", config->challengeuses,
149 name, "ntlm", config->challengelifetime);
150
151 }
152
153 static void
154 authNTLMParse(authScheme * scheme, int n_configured, char *param_str)
155 {
156 if (scheme->scheme_data == NULL) {
157 assert(ntlmConfig == NULL);
158 /* this is the first param to be found */
159 scheme->scheme_data = xmalloc(sizeof(auth_ntlm_config));
160 memset(scheme->scheme_data, 0, sizeof(auth_ntlm_config));
161 ntlmConfig = scheme->scheme_data;
162 ntlmConfig->authenticateChildren = 5;
163 ntlmConfig->challengeuses = 0;
164 ntlmConfig->challengelifetime = 60;
165 }
166 ntlmConfig = scheme->scheme_data;
167 if (strcasecmp(param_str, "program") == 0) {
168 parse_wordlist(&ntlmConfig->authenticate);
169 requirePathnameExists("authparam ntlm program", ntlmConfig->authenticate->key);
170 } else if (strcasecmp(param_str, "children") == 0) {
171 parse_int(&ntlmConfig->authenticateChildren);
172 } else if (strcasecmp(param_str, "max_challenge_reuses") == 0) {
173 parse_int(&ntlmConfig->challengeuses);
174 } else if (strcasecmp(param_str, "max_challenge_lifetime") == 0) {
175 parse_time_t(&ntlmConfig->challengelifetime);
176 } else {
177 debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str);
178 }
179 }
180
181
182 void
183 authSchemeSetup_ntlm(authscheme_entry_t * authscheme)
184 {
185 assert(!authntlm_initialised);
186 authscheme->Active = authenticateNTLMActive;
187 authscheme->parse = authNTLMParse;
188 authscheme->dump = authNTLMCfgDump;
189 authscheme->requestFree = authNTLMAURequestFree;
190 authscheme->freeconfig = authNTLMFreeConfig;
191 authscheme->init = authNTLMInit;
192 authscheme->authAuthenticate = authenticateNTLMAuthenticateUser;
193 authscheme->authenticated = authNTLMAuthenticated;
194 authscheme->authFixHeader = authenticateNTLMFixErrorHeader;
195 authscheme->FreeUser = authenticateNTLMFreeUser;
196 authscheme->authStart = authenticateNTLMStart;
197 authscheme->authStats = authenticateNTLMStats;
198 authscheme->authUserUsername = authenticateNTLMUsername;
199 authscheme->getdirection = authenticateNTLMDirection;
200 authscheme->decodeauth = authenticateDecodeNTLMAuth;
201 authscheme->donefunc = authNTLMDone;
202 authscheme->oncloseconnection = authenticateNTLMOnCloseConnection;
203 }
204
205 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
206 * config file */
207 static void
208 authNTLMInit(authScheme * scheme)
209 {
210 static int ntlminit = 0;
211 if (ntlmConfig->authenticate) {
212 if (!ntlm_helper_state_pool)
213 ntlm_helper_state_pool = memPoolCreate("NTLM Helper State data", sizeof(ntlm_helper_state_t));
214 if (!ntlm_user_pool)
215 ntlm_user_pool = memPoolCreate("NTLM Scheme User Data", sizeof(ntlm_user_t));
216 if (!ntlm_request_pool)
217 ntlm_request_pool = memPoolCreate("NTLM Scheme Request Data", sizeof(ntlm_request_t));
218 authntlm_initialised = 1;
219 if (ntlmauthenticators == NULL)
220 ntlmauthenticators = helperStatefulCreate("ntlmauthenticator");
221 if (!proxy_auth_cache)
222 proxy_auth_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
223 assert(proxy_auth_cache);
224 ntlmauthenticators->cmdline = ntlmConfig->authenticate;
225 ntlmauthenticators->n_to_start = ntlmConfig->authenticateChildren;
226 ntlmauthenticators->ipc_type = IPC_TCP_SOCKET;
227 ntlmauthenticators->datapool = ntlm_helper_state_pool;
228 ntlmauthenticators->IsAvailable = authenticateNTLMHelperServerAvailable;
229 ntlmauthenticators->OnEmptyQueue = authenticateNTLMHelperServerOnEmpty;
230 helperStatefulOpenServers(ntlmauthenticators);
231 /* TODO: In here send the initial YR to preinitialise the challenge cache */
232 /* Think about this... currently we ask when the challenge is needed. Better? */
233 if (!ntlminit) {
234 cachemgrRegister("ntlmauthenticator",
235 "User NTLM Authenticator Stats",
236 authenticateNTLMStats, 0, 1);
237 ntlminit++;
238 }
239 CBDATA_INIT_TYPE(authenticateStateData);
240 }
241 }
242
243 int
244 authenticateNTLMActive()
245 {
246 if ((ntlmConfig != NULL) && (ntlmConfig->authenticate != NULL) &&
247 (ntlmConfig->authenticateChildren != 0) && (ntlmConfig->challengeuses > -1)
248 && (ntlmConfig->challengelifetime > -1))
249 return 1;
250 return 0;
251 }
252
253 /* NTLM Scheme */
254
255 int
256 authenticateNTLMDirection(auth_user_request_t * auth_user_request)
257 {
258 ntlm_request_t *ntlm_request = auth_user_request->scheme_data;
259 /* null auth_user is checked for by authenticateDirection */
260 switch (ntlm_request->auth_state) {
261 case AUTHENTICATE_STATE_NONE: /* no progress at all. */
262 debug(28, 1) ("authenticateNTLMDirection: called before NTLM Authenticate!. Report a bug to squid-dev.\n");
263 return -2;
264 case AUTHENTICATE_STATE_NEGOTIATE: /* send to helper */
265 case AUTHENTICATE_STATE_RESPONSE: /*send to helper */
266 return -1;
267 case AUTHENTICATE_STATE_CHALLENGE: /* send to client */
268 return 1;
269 case AUTHENTICATE_STATE_DONE: /* do nothing.. */
270 return 0;
271 }
272 return -2;
273 }
274
275 /*
276 * Send the authenticate error header(s). Note: IE has a bug and the NTLM header
277 * must be first. To ensure that, the configure use --enable-auth=ntlm, anything
278 * else.
279 */
280 void
281 authenticateNTLMFixErrorHeader(auth_user_request_t * auth_user_request, HttpReply * rep, http_hdr_type type, request_t * request)
282 {
283 ntlm_request_t *ntlm_request;
284 if (ntlmConfig->authenticate) {
285 /* New request, no user details */
286 if (auth_user_request == NULL) {
287 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type);
288 httpHeaderPutStrf(&rep->header, type, "NTLM");
289 /* drop the connection */
290 httpHeaderDelByName(&rep->header, "keep-alive");
291 /* NTLM has problems if the initial connection is not dropped
292 * I haven't checked the RFC compliance of this hack - RBCollins */
293 request->flags.proxy_keepalive = 0;
294 } else {
295 ntlm_request = auth_user_request->scheme_data;
296 switch (ntlm_request->auth_state) {
297 case AUTHENTICATE_STATE_NONE:
298 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type);
299 httpHeaderPutStrf(&rep->header, type, "NTLM");
300 /* drop the connection */
301 httpHeaderDelByName(&rep->header, "keep-alive");
302 /* NTLM has problems if the initial connection is not dropped
303 * I haven't checked the RFC compliance of this hack - RBCollins */
304 request->flags.proxy_keepalive = 0;
305 break;
306 case AUTHENTICATE_STATE_CHALLENGE:
307 /* we are 'waiting' for a response */
308 /* pass the challenge to the client */
309 debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type, ntlm_request->authchallenge);
310 httpHeaderPutStrf(&rep->header, type, "NTLM %s", ntlm_request->authchallenge);
311 break;
312 default:
313 debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request->auth_state);
314 fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
315 }
316 }
317 }
318 }
319
320 void
321 authNTLMRequestFree(ntlm_request_t * ntlm_request)
322 {
323 if (!ntlm_request)
324 return;
325 if (ntlm_request->ntlmnegotiate)
326 xfree(ntlm_request->ntlmnegotiate);
327 if (ntlm_request->authchallenge)
328 xfree(ntlm_request->authchallenge);
329 if (ntlm_request->ntlmauthenticate)
330 xfree(ntlm_request->ntlmauthenticate);
331 memPoolFree(ntlm_request_pool, ntlm_request);
332 }
333
334 void
335 authNTLMAURequestFree(auth_user_request_t * auth_user_request)
336 {
337 if (auth_user_request->scheme_data)
338 authNTLMRequestFree((ntlm_request_t *) auth_user_request->scheme_data);
339 auth_user_request->scheme_data = NULL;
340 }
341
342 void
343 authenticateNTLMFreeUser(auth_user_t * auth_user)
344 {
345 dlink_node *link, *tmplink;
346 ntlm_user_t *ntlm_user = auth_user->scheme_data;
347 auth_user_hash_pointer *proxy_auth_hash;
348
349 debug(29, 5) ("authenticateNTLMFreeUser: Clearing NTLM scheme data\n");
350 if (ntlm_user->username)
351 xfree(ntlm_user->username);
352 /* were they linked in by one or more proxy-authenticate headers */
353 link = ntlm_user->proxy_auth_list.head;
354 while (link) {
355 debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%d'\n", link->data);
356 proxy_auth_hash = link->data;
357 tmplink = link;
358 link = link->next;
359 dlinkDelete(tmplink, &ntlm_user->proxy_auth_list);
360 hash_remove_link(proxy_auth_cache, (hash_link *) proxy_auth_hash);
361 /* free the key (usually the proxy_auth header) */
362 xfree(proxy_auth_hash->key);
363 memFree(proxy_auth_hash, MEM_AUTH_USER_HASH);
364 }
365 memPoolFree(ntlm_user_pool, ntlm_user);
366 auth_user->scheme_data = NULL;
367 }
368
369 static stateful_helper_callback_t
370 authenticateNTLMHandleplaceholder(void *data, void *lastserver, char *reply)
371 {
372 authenticateStateData *r = data;
373 stateful_helper_callback_t result = S_HELPER_UNKNOWN;
374 int valid;
375 /* we should only be called for placeholder requests - which have no reply string */
376 assert(reply == NULL);
377 assert(r->auth_user_request);
378 /* standard callback stuff */
379 valid = cbdataValid(r->data);
380 /* call authenticateNTLMStart to retry this request */
381 debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n");
382 authenticateNTLMStart(r->auth_user_request, r->handler, r->data);
383 cbdataUnlock(r->data);
384 authenticateStateFree(r);
385 return result;
386 }
387
388 static stateful_helper_callback_t
389 authenticateNTLMHandleReply(void *data, void *lastserver, char *reply)
390 {
391 authenticateStateData *r = data;
392 ntlm_helper_state_t *helperstate;
393 int valid;
394 stateful_helper_callback_t result = S_HELPER_UNKNOWN;
395 char *t = NULL;
396 auth_user_request_t *auth_user_request;
397 auth_user_t *auth_user;
398 ntlm_user_t *ntlm_user;
399 ntlm_request_t *ntlm_request;
400 debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%d' {%s}\n", lastserver, reply ? reply : "<NULL>");
401 valid = cbdataValid(r->data);
402 if (valid) {
403 if (reply) {
404 /* seperate out the useful data */
405 if (strncasecmp(reply, "TT ", 3) == 0) {
406 reply += 3;
407 /* we have been given a Challenge */
408 /* we should check we weren't given an empty challenge */
409 /* copy the challenge to the state data */
410 helperstate = helperStatefulServerGetData(lastserver);
411 if (helperstate == NULL)
412 fatal("lost NTLm helper state! quitting\n");
413 helperstate->challenge = xstrndup(reply, NTLM_CHALLENGE_SZ + 5);
414 helperstate->challengeuses = 0;
415 helperstate->renewed = squid_curtime;
416 /* and we satisfy the request that happended on the refresh boundary */
417 /* note this code is now in two places FIXME */
418 assert(r->auth_user_request != NULL);
419 assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM);
420 auth_user_request = r->auth_user_request;
421 ntlm_request = auth_user_request->scheme_data;
422 assert(ntlm_request != NULL);
423 result = S_HELPER_DEFER;
424 debug(29, 9) ("authenticateNTLMHandleReply: helper '%d'\n", lastserver);
425 assert(ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE);
426 ntlm_request->authhelper = lastserver;
427 ntlm_request->authchallenge = xstrndup(reply, NTLM_CHALLENGE_SZ + 5);
428 } else if (strncasecmp(reply, "AF ", 3) == 0) {
429 /* we're finished, release the helper */
430 reply += 3;
431 assert(r->auth_user_request != NULL);
432 assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM);
433 auth_user_request = r->auth_user_request;
434 assert(auth_user_request->scheme_data != NULL);
435 ntlm_request = auth_user_request->scheme_data;
436 auth_user = auth_user_request->auth_user;
437 ntlm_user = auth_user_request->auth_user->scheme_data;
438 assert(ntlm_user != NULL);
439 result = S_HELPER_RELEASE;
440 /* we only expect OK when finishing the handshake */
441 assert(ntlm_request->auth_state == AUTHENTICATE_STATE_RESPONSE);
442 ntlm_user->username = xstrndup(reply, MAX_LOGIN_SZ);
443 ntlm_request->authhelper = NULL;
444 auth_user->flags.credentials_ok = 1; /* login ok */
445 } else if (strncasecmp(reply, "NA ", 3) == 0) {
446 /* TODO: only work with auth_user here if it exists */
447 assert(r->auth_user_request != NULL);
448 assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM);
449 auth_user_request = r->auth_user_request;
450 auth_user = auth_user_request->auth_user;
451 assert(auth_user != NULL);
452 ntlm_user = auth_user->scheme_data;
453 ntlm_request = auth_user_request->scheme_data;
454 assert((ntlm_user != NULL) && (ntlm_request != NULL));
455 /* todo: action of Negotiate state on error */
456 result = S_HELPER_RELEASE; /*some error has occured. no more requests */
457 ntlm_request->authhelper = NULL;
458 auth_user->flags.credentials_ok = 2; /* Login/Usercode failed */
459 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n");
460 ntlm_request->auth_state = AUTHENTICATE_STATE_NONE;
461 if ((t = strchr(reply, ' '))) /* strip after a space */
462 *t = '\0';
463 } else if (strncasecmp(reply, "BH ", 3) == 0) {
464 /* TODO kick off a refresh process. This can occur after a YR or after
465 * a KK. If after a YR release the helper and resubmit the request via
466 * Authenticate NTLM start.
467 * If after a KK deny the user's request w/ 407 and mark the helper as
468 * Needing YR. */
469 assert(r->auth_user_request != NULL);
470 assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM);
471 auth_user_request = r->auth_user_request;
472 auth_user = auth_user_request->auth_user;
473 assert(auth_user != NULL);
474 ntlm_user = auth_user->scheme_data;
475 ntlm_request = auth_user_request->scheme_data;
476 assert((ntlm_user != NULL) && (ntlm_request != NULL));
477 result = S_HELPER_RELEASE; /*some error has occured. no more requests for
478 * this helper */
479 helperstate = helperStatefulServerGetData(ntlm_request->authhelper);
480 ntlm_request->authhelper = NULL;
481 if (ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE) {
482 /* The helper broke on YR. It automatically
483 * resets */
484 auth_user->flags.credentials_ok = 3; /* cannot process */
485 debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %d.\n", lastserver);
486 /* mark it for starving */
487 helperstate->starve = 1;
488 /* resubmit the request. This helper is currently busy, so we will get
489 * a different one. */
490 authenticateNTLMStart(auth_user_request, r->handler, r->data);
491 } else {
492 /* the helper broke on a KK */
493 /* first the standard KK stuff */
494 auth_user->flags.credentials_ok = 2; /* Login/Usercode failed */
495 debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n");
496 ntlm_request->auth_state = AUTHENTICATE_STATE_NONE;
497 if ((t = strchr(reply, ' '))) /* strip after a space */
498 *t = '\0';
499 /* now we mark the helper for resetting. */
500 helperstate->starve = 1;
501 }
502 ntlm_request->auth_state = AUTHENTICATE_STATE_NONE;
503 } else {
504 /* TODO: only work with auth_user here if it exists */
505 assert(r->auth_user_request != NULL);
506 assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM);
507 auth_user_request = r->auth_user_request;
508 auth_user = auth_user_request->auth_user;
509 assert(auth_user != NULL);
510 ntlm_user = auth_user->scheme_data;
511 ntlm_request = auth_user_request->scheme_data;
512 assert((ntlm_user != NULL) && (ntlm_request != NULL));
513 debug(29, 1) ("authenticateNTLMHandleReply: Unsupported helper response, '%s'\n", reply);
514 /* restart the authentication process */
515 ntlm_request->auth_state = AUTHENTICATE_STATE_NONE;
516 auth_user->flags.credentials_ok = 3; /* cannot process */
517 ntlm_request->authhelper = NULL;
518 }
519 } else {
520 fatal("authenticateNTLMHandleReply: called with no result string\n");
521 }
522 r->handler(r->data, NULL);
523 } else {
524 debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%d'.\n", lastserver);
525 result = S_HELPER_RELEASE;
526 }
527 cbdataUnlock(r->data);
528 authenticateStateFree(r);
529 debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result);
530 return result;
531 }
532
533 static void
534 authenticateNTLMStats(StoreEntry * sentry)
535 {
536 storeAppendPrintf(sentry, "NTLM Authenticator Statistics:\n");
537 helperStatefulStats(sentry, ntlmauthenticators);
538 }
539
540 /* is a particular challenge still valid ? */
541 int
542 authenticateNTLMValidChallenge(ntlm_helper_state_t * helperstate)
543 {
544 if (helperstate->challenge == NULL)
545 return 0;
546 return 1;
547 }
548
549 /* does our policy call for changing the challenge now? */
550 int
551 authenticateNTLMChangeChallenge(ntlm_helper_state_t * helperstate)
552 {
553 /* don't check for invalid challenges just for expiry choices */
554 /* this is needed because we have to starve the helper until all old
555 * requests have been satisfied */
556 if (helperstate->challengeuses > ntlmConfig->challengeuses)
557 return 1;
558 if (helperstate->renewed + ntlmConfig->challengelifetime >= squid_curtime)
559 return 1;
560 return 0;
561 }
562
563 /* send the initial data to a stateful ntlm authenticator module */
564 static void
565 authenticateNTLMStart(auth_user_request_t * auth_user_request, RH * handler, void *data)
566 {
567 authenticateStateData *r = NULL;
568 helper_stateful_server *server;
569 ntlm_helper_state_t *helperstate;
570 char buf[8192];
571 char *sent_string = NULL;
572 ntlm_user_t *ntlm_user;
573 ntlm_request_t *ntlm_request;
574 auth_user_t *auth_user;
575
576 assert(auth_user_request);
577 auth_user = auth_user_request->auth_user;
578 ntlm_user = auth_user->scheme_data;
579 ntlm_request = auth_user_request->scheme_data;
580 assert(ntlm_user);
581 assert(ntlm_request);
582 assert(handler);
583 assert(data);
584 assert(auth_user->auth_type = AUTH_NTLM);
585 debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", ntlm_request->auth_state);
586 switch (ntlm_request->auth_state) {
587 case AUTHENTICATE_STATE_NEGOTIATE:
588 sent_string = xstrdup(ntlm_request->ntlmnegotiate);
589 break;
590 case AUTHENTICATE_STATE_RESPONSE:
591 sent_string = xstrdup(ntlm_request->ntlmauthenticate);
592 assert(ntlm_request->authhelper);
593 debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%d'.\n", ntlm_request->authhelper);
594 break;
595 default:
596 fatal("Invalid authenticate state for NTLMStart");
597 }
598
599 while (!xisspace(*sent_string)) /*trim NTLM */
600 sent_string++;
601
602 while (xisspace(*sent_string)) /*trim leading spaces */
603 sent_string++;
604
605 debug(29, 9) ("authenticateNTLMStart: state '%d'\n", ntlm_request->auth_state);
606 debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string);
607 if (ntlmConfig->authenticate == NULL) {
608 debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string);
609 handler(data, NULL);
610 return;
611 }
612 #ifdef NTLMHELPPROTOCOLV2
613 r = CBDATA_ALLOC(authenticateStateData, NULL);
614 r->handler = handler;
615 cbdataLock(data);
616 r->data = data;
617 r->auth_user_request = auth_user_request;
618 snprintf(buf, 8192, "%s\n", sent_string);
619 helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authhelper);
620 debug(29, 9) ("authenticateNTLMstart: finished\n");
621 #else
622 /* this is ugly TODO: move the challenge generation routines to their own function and
623 * tidy the logic up to make use of the efficiency we now have */
624 switch (ntlm_request->auth_state) {
625 case AUTHENTICATE_STATE_NEGOTIATE:
626 /*
627 * 1: get a helper server
628 * 2: does it have a challenge?
629 * 3: tell it to get a challenge, or give ntlmauthdone the challenge
630 */
631 server = helperStatefulDefer(ntlmauthenticators);
632 helperstate = server ? helperStatefulServerGetData(server) : NULL;
633 while ((server != NULL) &&
634 authenticateNTLMChangeChallenge(helperstate)) {
635 /* flag this helper for challenge changing */
636 helperstate->starve = 1;
637 /* and release the deferred request */
638 helperStatefulReleaseServer(server);
639 server = helperStatefulDefer(ntlmauthenticators);
640 if (server != NULL)
641 helperstate = helperStatefulServerGetData(server);
642 }
643 if (server == NULL)
644 debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n");
645
646 ntlm_request->authhelper = server;
647 /* tell the log what helper we have been given */
648 debug(29, 9) ("authenticateNTLMStart: helper '%d' assigned\n", server);
649 /* valid challenge? */
650 if ((server == NULL) || !authenticateNTLMValidChallenge(helperstate)) {
651 r = CBDATA_ALLOC(authenticateStateData, NULL);
652 r->handler = handler;
653 cbdataLock(data);
654 r->data = data;
655 r->auth_user_request = auth_user_request;
656 if (server == NULL) {
657 helperStatefulSubmit(ntlmauthenticators, NULL, authenticateNTLMHandleplaceholder, r, ntlm_request->authhelper);
658 } else {
659 snprintf(buf, 8192, "YR\n");
660 helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authhelper);
661 }
662 } else {
663 /* we have a valid challenge */
664 /* TODO: turn the below into a function and call from here and handlereply */
665 /* increment the challenge uses */
666 helperstate->challengeuses++;
667 /* assign the challenge */
668 ntlm_request->authchallenge =
669 xstrndup(helperstate->challenge, NTLM_CHALLENGE_SZ + 5);
670 handler(data, NULL);
671 }
672
673 break;
674 case AUTHENTICATE_STATE_RESPONSE:
675 r = CBDATA_ALLOC(authenticateStateData, NULL);
676 r->handler = handler;
677 cbdataLock(data);
678 r->data = data;
679 r->auth_user_request = auth_user_request;
680 snprintf(buf, 8192, "KK %s\n", sent_string);
681 helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authhelper);
682 debug(29, 9) ("authenticateNTLMstart: finished\n");
683 break;
684 default:
685 fatal("Invalid authenticate state for NTLMStart");
686 }
687 #endif
688 }
689
690 /* callback used by stateful helper routines */
691 int
692 authenticateNTLMHelperServerAvailable(void *data)
693 {
694 ntlm_helper_state_t *statedata = data;
695 if (statedata != NULL) {
696 if (statedata->starve) {
697 debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n");
698 return 0;
699 } else {
700 debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n");
701 return 1;
702 }
703 }
704 debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n");
705 return 0;
706 }
707
708 void
709 authenticateNTLMHelperServerOnEmpty(void *data)
710 {
711 ntlm_helper_state_t *statedata = data;
712 if (statedata == NULL)
713 return;
714 if (statedata->starve) {
715 /* we have been starving the helper */
716 debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n");
717 statedata->starve = 0;
718 statedata->challengeuses = 0;
719 statedata->renewed = 0;
720 xfree(statedata->challenge);
721 statedata->challenge = NULL;
722 }
723 }
724
725
726 /* clear the NTLM helper of being reserved for future requests */
727 void
728 authenticateNTLMReleasehelper(auth_user_request_t * auth_user_request)
729 {
730 ntlm_request_t *ntlm_request;
731 assert(auth_user_request->auth_user->auth_type == AUTH_NTLM);
732 assert(auth_user_request->scheme_data != NULL);
733 ntlm_request = auth_user_request->scheme_data;
734 debug(29, 9) ("authenticateNTLMReleasehelper: releasing helper '%d'\n", ntlm_request->authhelper);
735 helperStatefulReleaseServer(ntlm_request->authhelper);
736 ntlm_request->authhelper = NULL;
737 }
738
739 /* clear any connection related authentication details */
740 void
741 authenticateNTLMOnCloseConnection(ConnStateData * conn)
742 {
743 ntlm_request_t *ntlm_request;
744 assert(conn != NULL);
745 if (conn->auth_user_request != NULL) {
746 assert(conn->auth_user_request->scheme_data != NULL);
747 ntlm_request = conn->auth_user_request->scheme_data;
748 if (ntlm_request->authhelper != NULL)
749 authenticateNTLMReleasehelper(conn->auth_user_request);
750 /* unlock the connection based lock */
751 debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n");
752 authenticateAuthUserRequestUnlock(conn->auth_user_request);
753 conn->auth_user_request = NULL;
754 }
755 }
756
757 /* authenticateUserUsername: return a pointer to the username in the */
758 char *
759 authenticateNTLMUsername(auth_user_t * auth_user)
760 {
761 ntlm_user_t *ntlm_user = auth_user->scheme_data;
762 if (ntlm_user)
763 return ntlm_user->username;
764 return NULL;
765 }
766
767
768 /*
769 * Decode an NTLM [Proxy-]Auth string, placing the results in the passed
770 * Auth_user structure.
771 */
772
773 void
774 authenticateDecodeNTLMAuth(auth_user_request_t * auth_user_request, const char *proxy_auth)
775 {
776 dlink_node *node;
777 assert(auth_user_request->auth_user == NULL);
778 auth_user_request->auth_user = authenticateAuthUserNew("ntlm");
779 auth_user_request->auth_user->auth_type = AUTH_NTLM;
780 auth_user_request->auth_user->scheme_data = memPoolAlloc(ntlm_user_pool);
781 auth_user_request->scheme_data = memPoolAlloc(ntlm_request_pool);
782 /* lock for the auth_user_request link */
783 authenticateAuthUserLock(auth_user_request->auth_user);
784 node = dlinkNodeNew();
785 dlinkAdd(auth_user_request, node, &auth_user_request->auth_user->requests);
786
787 /* all we have to do is identify that it's NTLM - the helper does the rest */
788 debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n");
789 return;
790 }
791
792 int
793 authenticateNTLMcmpUsername(ntlm_user_t * u1, ntlm_user_t * u2)
794 {
795 return strcmp(u1->username, u2->username);
796 }
797
798 void
799 authenticateProxyAuthCacheAddLink(const char *key, auth_user_t * auth_user)
800 {
801 auth_user_hash_pointer *proxy_auth_hash;
802 ntlm_user_t *ntlm_user;
803 proxy_auth_hash =
804 memAllocate(MEM_AUTH_USER_HASH);
805 proxy_auth_hash->key = xstrdup(key);
806 proxy_auth_hash->auth_user = auth_user;
807 ntlm_user = auth_user->scheme_data;
808 dlinkAddTail(proxy_auth_hash, &proxy_auth_hash->link,
809 &ntlm_user->proxy_auth_list);
810 hash_join(proxy_auth_cache, (hash_link *) proxy_auth_hash);
811 }
812
813
814 int
815 authNTLMAuthenticated(auth_user_request_t * auth_user_request)
816 {
817 ntlm_request_t *ntlm_request = auth_user_request->scheme_data;
818 if (ntlm_request->auth_state == AUTHENTICATE_STATE_DONE)
819 return 1;
820 debug(29, 9) ("User not fully authenticated.\n");
821 return 0;
822 }
823
824 static void
825 authenticateNTLMAuthenticateUser(auth_user_request_t * auth_user_request, request_t * request, ConnStateData * conn, http_hdr_type type)
826 {
827 const char *proxy_auth;
828 auth_user_hash_pointer *usernamehash, *proxy_auth_hash = NULL;
829 auth_user_t *auth_user;
830 ntlm_request_t *ntlm_request;
831 ntlm_user_t *ntlm_user;
832 LOCAL_ARRAY(char, ntlmhash, NTLM_CHALLENGE_SZ * 2);
833 /* get header */
834 proxy_auth = httpHeaderGetStr(&request->header, type);
835
836 auth_user = auth_user_request->auth_user;
837 assert(auth_user);
838 assert(auth_user->auth_type == AUTH_NTLM);
839 assert(auth_user->scheme_data != NULL);
840 assert(auth_user_request->scheme_data != NULL);
841 ntlm_user = auth_user->scheme_data;
842 ntlm_request = auth_user_request->scheme_data;
843 switch (ntlm_request->auth_state) {
844 case AUTHENTICATE_STATE_NONE:
845 /* we've recieved a negotiate request. pass to a helper */
846 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n",
847 proxy_auth);
848 ntlm_request->auth_state = AUTHENTICATE_STATE_NEGOTIATE;
849 ntlm_request->ntlmnegotiate = xstrndup(proxy_auth, NTLM_CHALLENGE_SZ + 5);
850 conn->auth_type = AUTH_NTLM;
851 conn->auth_user_request = auth_user_request;
852 /* and lock for the connection duration */
853 debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n");
854 authenticateAuthUserRequestLock(auth_user_request);
855 return;
856 break;
857 case AUTHENTICATE_STATE_NEGOTIATE:
858 ntlm_request->auth_state = AUTHENTICATE_STATE_CHALLENGE;
859 return;
860 break;
861 case AUTHENTICATE_STATE_CHALLENGE:
862 /* we should have recieved a NTLM challenge. pass it to the same
863 * helper process */
864 debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth);
865 /* do a cache lookup here. If it matches it's a successful ntlm
866 * challenge - release the helper and use the existing auth_user
867 * details. */
868 if (strncmp("NTLM ", proxy_auth, 5) == 0) {
869 ntlm_request->ntlmauthenticate = xstrdup(proxy_auth);
870 } else {
871 fatal("Incorrect scheme in auth header\n");
872 /* TODO: more fault tolerance.. reset the auth scheme here */
873 }
874 /* cache entries have authenticateauthheaderchallengestring */
875 snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s",
876 ntlm_request->ntlmauthenticate,
877 ntlm_request->authchallenge);
878 /* see if we already know this user's authenticate */
879 debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash);
880 assert(proxy_auth_cache != NULL);
881 proxy_auth_hash = hash_lookup(proxy_auth_cache, ntlmhash);
882 if (!proxy_auth_hash) { /* not in the hash table */
883 debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n");
884 ntlm_request->auth_state = AUTHENTICATE_STATE_RESPONSE;
885 /* verify with the ntlm helper */
886 } else {
887 debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n");
888 /* throw away the temporary entry */
889 authenticateNTLMReleasehelper(auth_user_request);
890 authenticateAuthUserMerge(auth_user, proxy_auth_hash->auth_user);
891 auth_user = proxy_auth_hash->auth_user;
892 auth_user_request->auth_user = auth_user;
893 ntlm_request->auth_state = AUTHENTICATE_STATE_DONE;
894 /* we found one */
895 debug(29, 9) ("found matching cache entry\n");
896 assert(auth_user->auth_type == AUTH_NTLM);
897 /* get the existing entries details */
898 ntlm_user = auth_user->scheme_data;
899 debug(29, 9) ("Username to be used is %s\n",
900 ntlm_user->username);
901 auth_user->flags.credentials_ok = 1; /* authenticated ok */
902 /* on ntlm auth we do not unlock the auth_user until the
903 * connection is dropped. Thank MS for this quirk */
904 auth_user->expiretime = current_time.tv_sec;
905 auth_user->ip_expiretime = squid_curtime;
906 }
907 return;
908 break;
909 case AUTHENTICATE_STATE_RESPONSE:
910 /* auth-challenge pair cache miss. We've just got the response */
911 /*add to cache and let them through */
912 ntlm_request->auth_state = AUTHENTICATE_STATE_DONE;
913 /* this connection is authenticated */
914 debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n",
915 ntlm_request->authchallenge,
916 ntlm_request->ntlmauthenticate,
917 ntlm_user->username);
918 /* cache entries have authenticateauthheaderchallengestring */
919 snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s",
920 ntlm_request->ntlmauthenticate,
921 ntlm_request->authchallenge);
922 /* see if this is an existing user with a different proxy_auth
923 * string */
924 if ((usernamehash = hash_lookup(proxy_auth_username_cache,
925 ntlm_user->username))) {
926 while ((usernamehash->auth_user->auth_type !=
927 auth_user->auth_type) && (usernamehash->next) &&
928 !authenticateNTLMcmpUsername(usernamehash->auth_user->scheme_data, ntlm_user))
929 usernamehash = usernamehash->next;
930 if (usernamehash->auth_user->auth_type == auth_user->auth_type) {
931 /*
932 * add another link from the new proxy_auth to the
933 * auth_user structure and update the information */
934 assert(proxy_auth_hash == NULL);
935 authenticateProxyAuthCacheAddLink(ntlmhash, usernamehash->auth_user);
936 /* we can't seamlessly recheck the username due to the
937 * challenge nature of the protocol. Just free the
938 * temporary auth_user */
939 authenticateAuthUserMerge(auth_user, usernamehash->auth_user);
940 auth_user = usernamehash->auth_user;
941 auth_user_request->auth_user = auth_user;
942 }
943 } else {
944 /* store user in hash's */
945 authenticateUserNameCacheAdd(auth_user);
946 authenticateProxyAuthCacheAddLink(ntlmhash, auth_user);
947 }
948 /* set these to now because this is either a new login from an
949 * existing user or a new user */
950 auth_user->expiretime = current_time.tv_sec;
951 auth_user->ip_expiretime = squid_curtime;
952 auth_user->flags.credentials_ok = 1; /*authenticated ok */
953 return;
954 break;
955 case AUTHENTICATE_STATE_DONE:
956 fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n");
957 }
958
959 return;
960 }