]>
Commit | Line | Data |
---|---|---|
94439e4e | 1 | |
2 | /* | |
9970d4b1 | 3 | * $Id: auth_ntlm.cc,v 1.39 2004/04/08 00:48:29 hno Exp $ |
94439e4e | 4 | * |
5 | * DEBUG: section 29 NTLM Authenticator | |
6 | * AUTHOR: Robert Collins | |
7 | * | |
2b6662ba | 8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ |
94439e4e | 9 | * ---------------------------------------------------------- |
10 | * | |
2b6662ba | 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. | |
94439e4e | 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" | |
e6ccf245 | 43 | #include "authenticate.h" |
44 | #include "Store.h" | |
a46d2c0e | 45 | #include "client_side.h" |
924f73bc | 46 | #include "HttpReply.h" |
a2ac85d9 | 47 | #include "HttpRequest.h" |
94439e4e | 48 | |
c78aa667 | 49 | extern AUTHSSETUP authSchemeSetup_ntlm; |
50 | ||
94439e4e | 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 AUTHSACTIVE authenticateNTLMActive; | |
94439e4e | 61 | static AUTHSAUTHUSER authenticateNTLMAuthenticateUser; |
2d70df72 | 62 | static AUTHSCONFIGURED authNTLMConfigured; |
94439e4e | 63 | static AUTHSFIXERR authenticateNTLMFixErrorHeader; |
64 | static AUTHSFREE authenticateNTLMFreeUser; | |
94439e4e | 65 | static AUTHSDECODE authenticateDecodeNTLMAuth; |
66 | static AUTHSDUMP authNTLMCfgDump; | |
67 | static AUTHSFREECONFIG authNTLMFreeConfig; | |
68 | static AUTHSINIT authNTLMInit; | |
69 | static AUTHSONCLOSEC authenticateNTLMOnCloseConnection; | |
60d096f4 | 70 | static AUTHSCONNLASTHEADER NTLMLastHeader; |
94439e4e | 71 | static AUTHSUSERNAME authenticateNTLMUsername; |
94439e4e | 72 | static AUTHSPARSE authNTLMParse; |
73 | static AUTHSSTART authenticateNTLMStart; | |
74 | static AUTHSSTATS authenticateNTLMStats; | |
75 | static AUTHSSHUTDOWN authNTLMDone; | |
76 | ||
77 | /* helper callbacks to handle per server state data */ | |
78 | static HLPSAVAIL authenticateNTLMHelperServerAvailable; | |
79 | static HLPSONEQ authenticateNTLMHelperServerOnEmpty; | |
80 | ||
81 | static statefulhelper *ntlmauthenticators = NULL; | |
82 | ||
83 | CBDATA_TYPE(authenticateStateData); | |
84 | ||
85 | static int authntlm_initialised = 0; | |
86 | ||
c78aa667 | 87 | static MemPool *ntlm_helper_state_pool = NULL; |
88 | static MemPool *ntlm_user_pool = NULL; | |
e6ccf245 | 89 | static MemPool *ntlm_user_hash_pool = NULL; |
90 | ||
94439e4e | 91 | static auth_ntlm_config *ntlmConfig = NULL; |
92 | ||
93 | static hash_table *proxy_auth_cache = NULL; | |
94 | ||
95 | /* | |
96 | * | |
97 | * Private Functions | |
98 | * | |
99 | */ | |
100 | ||
c78aa667 | 101 | static void |
94439e4e | 102 | authNTLMDone(void) |
103 | { | |
2d70df72 | 104 | debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n"); |
62e76326 | 105 | |
bd507204 | 106 | if (ntlmauthenticators) |
62e76326 | 107 | helperStatefulShutdown(ntlmauthenticators); |
108 | ||
94439e4e | 109 | authntlm_initialised = 0; |
62e76326 | 110 | |
94439e4e | 111 | if (!shutting_down) |
62e76326 | 112 | return; |
113 | ||
bd507204 | 114 | if (ntlmauthenticators) |
62e76326 | 115 | helperStatefulFree(ntlmauthenticators); |
116 | ||
94439e4e | 117 | ntlmauthenticators = NULL; |
62e76326 | 118 | |
2f44bd34 | 119 | #if DEBUGSHUTDOWN |
62e76326 | 120 | |
5dae8514 | 121 | if (ntlm_helper_state_pool) { |
62e76326 | 122 | memPoolDestroy(&ntlm_helper_state_pool); |
5dae8514 | 123 | } |
62e76326 | 124 | |
5dae8514 | 125 | if (ntlm_user_pool) { |
62e76326 | 126 | memPoolDestroy(&ntlm_user_pool); |
5dae8514 | 127 | } |
62e76326 | 128 | |
2f44bd34 | 129 | #endif |
5dae8514 | 130 | debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n"); |
94439e4e | 131 | } |
132 | ||
133 | /* free any allocated configuration details */ | |
c78aa667 | 134 | static void |
94439e4e | 135 | authNTLMFreeConfig(authScheme * scheme) |
136 | { | |
137 | if (ntlmConfig == NULL) | |
62e76326 | 138 | return; |
139 | ||
94439e4e | 140 | assert(ntlmConfig == scheme->scheme_data); |
62e76326 | 141 | |
94439e4e | 142 | if (ntlmConfig->authenticate) |
62e76326 | 143 | wordlistDestroy(&ntlmConfig->authenticate); |
144 | ||
94439e4e | 145 | xfree(ntlmConfig); |
62e76326 | 146 | |
94439e4e | 147 | ntlmConfig = NULL; |
148 | } | |
149 | ||
150 | static void | |
151 | authNTLMCfgDump(StoreEntry * entry, const char *name, authScheme * scheme) | |
152 | { | |
e6ccf245 | 153 | auth_ntlm_config *config = static_cast<auth_ntlm_config *>(scheme->scheme_data); |
94439e4e | 154 | wordlist *list = config->authenticate; |
155 | storeAppendPrintf(entry, "%s %s", name, "ntlm"); | |
62e76326 | 156 | |
94439e4e | 157 | while (list != NULL) { |
62e76326 | 158 | storeAppendPrintf(entry, " %s", list->key); |
159 | list = list->next; | |
94439e4e | 160 | } |
62e76326 | 161 | |
94439e4e | 162 | storeAppendPrintf(entry, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n", |
62e76326 | 163 | name, "ntlm", config->authenticateChildren, |
164 | name, "ntlm", config->challengeuses, | |
165 | name, "ntlm", (int) config->challengelifetime); | |
94439e4e | 166 | |
167 | } | |
168 | ||
169 | static void | |
170 | authNTLMParse(authScheme * scheme, int n_configured, char *param_str) | |
171 | { | |
172 | if (scheme->scheme_data == NULL) { | |
62e76326 | 173 | assert(ntlmConfig == NULL); |
174 | /* this is the first param to be found */ | |
175 | scheme->scheme_data = xmalloc(sizeof(auth_ntlm_config)); | |
176 | memset(scheme->scheme_data, 0, sizeof(auth_ntlm_config)); | |
177 | ntlmConfig = static_cast<auth_ntlm_config *>(scheme->scheme_data); | |
178 | ntlmConfig->authenticateChildren = 5; | |
179 | ntlmConfig->challengeuses = 0; | |
180 | ntlmConfig->challengelifetime = 60; | |
94439e4e | 181 | } |
62e76326 | 182 | |
e6ccf245 | 183 | ntlmConfig = static_cast<auth_ntlm_config *>(scheme->scheme_data); |
62e76326 | 184 | |
94439e4e | 185 | if (strcasecmp(param_str, "program") == 0) { |
62e76326 | 186 | if (ntlmConfig->authenticate) |
187 | wordlistDestroy(&ntlmConfig->authenticate); | |
188 | ||
189 | parse_wordlist(&ntlmConfig->authenticate); | |
190 | ||
191 | requirePathnameExists("authparam ntlm program", ntlmConfig->authenticate->key); | |
94439e4e | 192 | } else if (strcasecmp(param_str, "children") == 0) { |
62e76326 | 193 | parse_int(&ntlmConfig->authenticateChildren); |
94439e4e | 194 | } else if (strcasecmp(param_str, "max_challenge_reuses") == 0) { |
62e76326 | 195 | parse_int(&ntlmConfig->challengeuses); |
94439e4e | 196 | } else if (strcasecmp(param_str, "max_challenge_lifetime") == 0) { |
62e76326 | 197 | parse_time_t(&ntlmConfig->challengelifetime); |
94439e4e | 198 | } else { |
62e76326 | 199 | debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str); |
94439e4e | 200 | } |
62e76326 | 201 | |
dff99376 | 202 | /* |
203 | * disable client side request pipelining. There is a race with | |
204 | * NTLM when the client sends a second request on an NTLM | |
205 | * connection before the authenticate challenge is sent. With | |
206 | * this patch, the client may fail to authenticate, but squid's | |
207 | * state will be preserved. Caveats: this should be a post-parse | |
208 | * test, but that can wait for the modular parser to be integrated. | |
721b0310 | 209 | */ |
210 | if (ntlmConfig->authenticate) | |
62e76326 | 211 | Config.onoff.pipeline_prefetch = 0; |
94439e4e | 212 | } |
213 | ||
214 | ||
215 | void | |
216 | authSchemeSetup_ntlm(authscheme_entry_t * authscheme) | |
217 | { | |
94439e4e | 218 | assert(!authntlm_initialised); |
219 | authscheme->Active = authenticateNTLMActive; | |
2d70df72 | 220 | authscheme->configured = authNTLMConfigured; |
94439e4e | 221 | authscheme->parse = authNTLMParse; |
222 | authscheme->dump = authNTLMCfgDump; | |
82b045dc | 223 | authscheme->requestFree = NULL; |
94439e4e | 224 | authscheme->freeconfig = authNTLMFreeConfig; |
225 | authscheme->init = authNTLMInit; | |
226 | authscheme->authAuthenticate = authenticateNTLMAuthenticateUser; | |
82b045dc | 227 | authscheme->authenticated = NULL; |
94439e4e | 228 | authscheme->authFixHeader = authenticateNTLMFixErrorHeader; |
229 | authscheme->FreeUser = authenticateNTLMFreeUser; | |
230 | authscheme->authStart = authenticateNTLMStart; | |
231 | authscheme->authStats = authenticateNTLMStats; | |
232 | authscheme->authUserUsername = authenticateNTLMUsername; | |
82b045dc | 233 | authscheme->getdirection = NULL; |
94439e4e | 234 | authscheme->decodeauth = authenticateDecodeNTLMAuth; |
235 | authscheme->donefunc = authNTLMDone; | |
236 | authscheme->oncloseconnection = authenticateNTLMOnCloseConnection; | |
60d096f4 | 237 | authscheme->authConnLastHeader = NTLMLastHeader; |
94439e4e | 238 | } |
239 | ||
240 | /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the | |
241 | * config file */ | |
242 | static void | |
243 | authNTLMInit(authScheme * scheme) | |
244 | { | |
245 | static int ntlminit = 0; | |
62e76326 | 246 | |
94439e4e | 247 | if (ntlmConfig->authenticate) { |
62e76326 | 248 | if (!ntlm_helper_state_pool) |
249 | ntlm_helper_state_pool = memPoolCreate("NTLM Helper State data", sizeof(ntlm_helper_state_t)); | |
250 | ||
251 | if (!ntlm_user_pool) | |
252 | ntlm_user_pool = memPoolCreate("NTLM Scheme User Data", sizeof(ntlm_user_t)); | |
253 | ||
62e76326 | 254 | if (!ntlm_user_hash_pool) |
255 | ||
256 | ntlm_user_hash_pool = memPoolCreate("NTLM Header Hash Data", sizeof(struct ProxyAuthCachePointer)); | |
257 | ||
258 | authntlm_initialised = 1; | |
259 | ||
260 | if (ntlmauthenticators == NULL) | |
261 | ntlmauthenticators = helperStatefulCreate("ntlmauthenticator"); | |
262 | ||
263 | if (!proxy_auth_cache) | |
264 | proxy_auth_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); | |
265 | ||
266 | assert(proxy_auth_cache); | |
267 | ||
268 | ntlmauthenticators->cmdline = ntlmConfig->authenticate; | |
269 | ||
270 | ntlmauthenticators->n_to_start = ntlmConfig->authenticateChildren; | |
271 | ||
272 | ntlmauthenticators->ipc_type = IPC_STREAM; | |
273 | ||
274 | ntlmauthenticators->datapool = ntlm_helper_state_pool; | |
275 | ||
276 | ntlmauthenticators->IsAvailable = authenticateNTLMHelperServerAvailable; | |
277 | ||
278 | ntlmauthenticators->OnEmptyQueue = authenticateNTLMHelperServerOnEmpty; | |
279 | ||
280 | helperStatefulOpenServers(ntlmauthenticators); | |
281 | ||
282 | /* | |
283 | * TODO: In here send the initial YR to preinitialise the | |
284 | * challenge cache | |
285 | */ | |
286 | /* | |
287 | * Think about this... currently we ask when the challenge | |
288 | * is needed. Better? | |
289 | */ | |
290 | if (!ntlminit) { | |
291 | cachemgrRegister("ntlmauthenticator", | |
292 | "NTLM User Authenticator Stats", | |
293 | authenticateNTLMStats, 0, 1); | |
294 | ntlminit++; | |
295 | } | |
296 | ||
297 | CBDATA_INIT_TYPE(authenticateStateData); | |
94439e4e | 298 | } |
299 | } | |
300 | ||
c78aa667 | 301 | static int |
94439e4e | 302 | authenticateNTLMActive() |
2d70df72 | 303 | { |
304 | return (authntlm_initialised == 1) ? 1 : 0; | |
305 | } | |
306 | ||
307 | ||
c78aa667 | 308 | static int |
2d70df72 | 309 | authNTLMConfigured() |
94439e4e | 310 | { |
6437ac71 | 311 | if ((ntlmConfig != NULL) && (ntlmConfig->authenticate != NULL) && (ntlmConfig->authenticateChildren != 0) && (ntlmConfig->challengeuses > -1) && (ntlmConfig->challengelifetime > -1)) { |
62e76326 | 312 | debug(29, 9) ("authNTLMConfigured: returning configured\n"); |
313 | return 1; | |
2d70df72 | 314 | } |
62e76326 | 315 | |
2d70df72 | 316 | debug(29, 9) ("authNTLMConfigured: returning unconfigured\n"); |
912b43b8 | 317 | return 0; |
94439e4e | 318 | } |
319 | ||
320 | /* NTLM Scheme */ | |
82b045dc | 321 | int |
322 | ntlm_request_t::direction() | |
94439e4e | 323 | { |
bd507204 | 324 | /* null auth_user is checked for by authenticateDirection */ |
62e76326 | 325 | |
82b045dc | 326 | switch (auth_state) { |
62e76326 | 327 | |
82b045dc | 328 | /* no progress at all. */ |
329 | ||
330 | case AUTHENTICATE_STATE_NONE: | |
331 | debug(29, 1) ("ntlm_request_t::direction: called before NTLM Authenticate!. Report a bug to squid-dev.\n"); | |
62e76326 | 332 | /* fall thru */ |
333 | ||
3c641669 | 334 | case AUTHENTICATE_STATE_FAILED: |
62e76326 | 335 | return -2; |
336 | ||
82b045dc | 337 | /* send to helper */ |
338 | ||
339 | case AUTHENTICATE_STATE_NEGOTIATE: | |
340 | ||
341 | /*send to helper */ | |
62e76326 | 342 | |
82b045dc | 343 | case AUTHENTICATE_STATE_RESPONSE: |
62e76326 | 344 | return -1; |
345 | ||
82b045dc | 346 | /* send to client */ |
347 | ||
348 | case AUTHENTICATE_STATE_CHALLENGE: | |
62e76326 | 349 | return 1; |
350 | ||
82b045dc | 351 | /* do nothing.. */ |
352 | ||
353 | case AUTHENTICATE_STATE_DONE: | |
62e76326 | 354 | return 0; |
94439e4e | 355 | } |
62e76326 | 356 | |
94439e4e | 357 | return -2; |
358 | } | |
359 | ||
360 | /* | |
361 | * Send the authenticate error header(s). Note: IE has a bug and the NTLM header | |
362 | * must be first. To ensure that, the configure use --enable-auth=ntlm, anything | |
363 | * else. | |
364 | */ | |
c78aa667 | 365 | static void |
190154cf | 366 | authenticateNTLMFixErrorHeader(auth_user_request_t * auth_user_request, HttpReply * rep, http_hdr_type type, HttpRequest * request) |
94439e4e | 367 | { |
368 | ntlm_request_t *ntlm_request; | |
62e76326 | 369 | |
94439e4e | 370 | if (ntlmConfig->authenticate) { |
62e76326 | 371 | /* New request, no user details */ |
372 | ||
373 | if (auth_user_request == NULL) { | |
374 | debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type); | |
375 | httpHeaderPutStrf(&rep->header, type, "NTLM"); | |
376 | /* drop the connection */ | |
377 | httpHeaderDelByName(&rep->header, "keep-alive"); | |
378 | /* NTLM has problems if the initial connection is not dropped | |
379 | * I haven't checked the RFC compliance of this hack - RBCollins */ | |
380 | request->flags.proxy_keepalive = 0; | |
381 | } else { | |
82b045dc | 382 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
383 | assert (ntlm_request); | |
62e76326 | 384 | |
385 | switch (ntlm_request->auth_state) { | |
386 | ||
387 | case AUTHENTICATE_STATE_NONE: | |
388 | ||
389 | case AUTHENTICATE_STATE_FAILED: | |
390 | debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type); | |
391 | httpHeaderPutStrf(&rep->header, type, "NTLM"); | |
392 | /* drop the connection */ | |
393 | httpHeaderDelByName(&rep->header, "keep-alive"); | |
394 | /* NTLM has problems if the initial connection is not dropped | |
395 | * I haven't checked the RFC compliance of this hack - RBCollins */ | |
396 | request->flags.proxy_keepalive = 0; | |
397 | break; | |
398 | ||
399 | case AUTHENTICATE_STATE_CHALLENGE: | |
400 | /* we are 'waiting' for a response */ | |
401 | /* pass the challenge to the client */ | |
402 | debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type, ntlm_request->authchallenge); | |
403 | httpHeaderPutStrf(&rep->header, type, "NTLM %s", ntlm_request->authchallenge); | |
404 | break; | |
405 | ||
406 | default: | |
407 | debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request->auth_state); | |
408 | fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n"); | |
409 | } | |
410 | } | |
94439e4e | 411 | } |
412 | } | |
413 | ||
82b045dc | 414 | ntlm_request_t::~ntlm_request_t() |
94439e4e | 415 | { |
82b045dc | 416 | if (ntlmnegotiate) |
417 | xfree(ntlmnegotiate); | |
62e76326 | 418 | |
82b045dc | 419 | if (authchallenge) |
420 | xfree(authchallenge); | |
62e76326 | 421 | |
82b045dc | 422 | if (ntlmauthenticate) |
423 | xfree(ntlmauthenticate); | |
62e76326 | 424 | |
82b045dc | 425 | if (authserver != NULL && authserver_deferred) { |
426 | debug(29, 9) ("authenticateNTLMRequestFree: releasing server '%p'\n", authserver); | |
427 | helperStatefulReleaseServer(authserver); | |
428 | authserver = NULL; | |
5d146f7d | 429 | } |
94439e4e | 430 | } |
431 | ||
c78aa667 | 432 | static void |
94439e4e | 433 | authenticateNTLMFreeUser(auth_user_t * auth_user) |
434 | { | |
435 | dlink_node *link, *tmplink; | |
e6ccf245 | 436 | ntlm_user_t *ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); |
437 | ProxyAuthCachePointer *proxy_auth_hash; | |
94439e4e | 438 | |
439 | debug(29, 5) ("authenticateNTLMFreeUser: Clearing NTLM scheme data\n"); | |
62e76326 | 440 | |
94439e4e | 441 | if (ntlm_user->username) |
62e76326 | 442 | xfree(ntlm_user->username); |
443 | ||
94439e4e | 444 | /* were they linked in by one or more proxy-authenticate headers */ |
445 | link = ntlm_user->proxy_auth_list.head; | |
62e76326 | 446 | |
94439e4e | 447 | while (link) { |
62e76326 | 448 | debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%p'\n", link->data); |
449 | proxy_auth_hash = static_cast<ProxyAuthCachePointer *>(link->data); | |
450 | tmplink = link; | |
451 | link = link->next; | |
452 | dlinkDelete(tmplink, &ntlm_user->proxy_auth_list); | |
453 | hash_remove_link(proxy_auth_cache, (hash_link *) proxy_auth_hash); | |
454 | /* free the key (usually the proxy_auth header) */ | |
455 | xfree(proxy_auth_hash->key); | |
456 | memPoolFree(ntlm_user_hash_pool, proxy_auth_hash); | |
94439e4e | 457 | } |
62e76326 | 458 | |
94439e4e | 459 | memPoolFree(ntlm_user_pool, ntlm_user); |
460 | auth_user->scheme_data = NULL; | |
461 | } | |
462 | ||
463 | static stateful_helper_callback_t | |
464 | authenticateNTLMHandleplaceholder(void *data, void *lastserver, char *reply) | |
465 | { | |
e6ccf245 | 466 | authenticateStateData *r = static_cast<authenticateStateData *>(data); |
94439e4e | 467 | stateful_helper_callback_t result = S_HELPER_UNKNOWN; |
94439e4e | 468 | /* we should only be called for placeholder requests - which have no reply string */ |
469 | assert(reply == NULL); | |
470 | assert(r->auth_user_request); | |
471 | /* standard callback stuff */ | |
62e76326 | 472 | |
fa80a8ef | 473 | if (!cbdataReferenceValid(r->data)) { |
62e76326 | 474 | debug(29, 1) ("AuthenticateNTLMHandlePlacheholder: invalid callback data.\n"); |
475 | return result; | |
60d096f4 | 476 | } |
62e76326 | 477 | |
94439e4e | 478 | /* call authenticateNTLMStart to retry this request */ |
479 | debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n"); | |
62e76326 | 480 | |
94439e4e | 481 | authenticateNTLMStart(r->auth_user_request, r->handler, r->data); |
62e76326 | 482 | |
fa80a8ef | 483 | cbdataReferenceDone(r->data); |
62e76326 | 484 | |
94439e4e | 485 | authenticateStateFree(r); |
62e76326 | 486 | |
94439e4e | 487 | return result; |
488 | } | |
489 | ||
490 | static stateful_helper_callback_t | |
491 | authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) | |
492 | { | |
e6ccf245 | 493 | authenticateStateData *r = static_cast<authenticateStateData *>(data); |
94439e4e | 494 | ntlm_helper_state_t *helperstate; |
94439e4e | 495 | stateful_helper_callback_t result = S_HELPER_UNKNOWN; |
94439e4e | 496 | char *t = NULL; |
497 | auth_user_request_t *auth_user_request; | |
498 | auth_user_t *auth_user; | |
499 | ntlm_user_t *ntlm_user; | |
500 | ntlm_request_t *ntlm_request; | |
33272404 | 501 | debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%p' {%s}\n", lastserver, reply ? reply : "<NULL>"); |
62e76326 | 502 | |
fa80a8ef | 503 | if (!cbdataReferenceValid(r->data)) { |
62e76326 | 504 | debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver); |
505 | cbdataReferenceDone(r->data); | |
506 | authenticateStateFree(r); | |
507 | debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", S_HELPER_RELEASE); | |
508 | return S_HELPER_RELEASE; | |
9bea1d5b | 509 | } |
62e76326 | 510 | |
5d146f7d | 511 | if (!reply) { |
62e76326 | 512 | /* |
513 | * TODO: this occurs when a helper crashes. We should clean | |
514 | * up that helpers resources and queued requests. | |
515 | */ | |
516 | fatal("authenticateNTLMHandleReply: called with no result string\n"); | |
9bea1d5b | 517 | } |
62e76326 | 518 | |
5d146f7d | 519 | /* seperate out the useful data */ |
520 | if (strncasecmp(reply, "TT ", 3) == 0) { | |
62e76326 | 521 | reply += 3; |
522 | /* we have been given a Challenge */ | |
523 | /* we should check we weren't given an empty challenge */ | |
524 | /* copy the challenge to the state data */ | |
525 | helperstate = static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(static_cast<helper_stateful_server *>(lastserver))); | |
526 | ||
527 | if (helperstate == NULL) | |
528 | fatal("lost NTLM helper state! quitting\n"); | |
529 | ||
530 | helperstate->challenge = xstrndup(reply, NTLM_CHALLENGE_SZ + 5); | |
531 | ||
532 | helperstate->challengeuses = 0; | |
533 | ||
534 | helperstate->renewed = squid_curtime; | |
535 | ||
536 | /* and we satisfy the request that happended on the refresh boundary */ | |
537 | /* note this code is now in two places FIXME */ | |
538 | assert(r->auth_user_request != NULL); | |
539 | ||
540 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
541 | ||
542 | auth_user_request = r->auth_user_request; | |
543 | ||
82b045dc | 544 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
62e76326 | 545 | |
546 | assert(ntlm_request != NULL); | |
547 | ||
548 | result = S_HELPER_DEFER; | |
549 | ||
550 | /* reserve the server for future authentication */ | |
551 | ntlm_request->authserver_deferred = 1; | |
552 | ||
553 | debug(29, 9) ("authenticateNTLMHandleReply: helper '%p'\n", lastserver); | |
554 | ||
555 | assert(ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE); | |
556 | ||
557 | ntlm_request->authserver = static_cast<helper_stateful_server *>(lastserver); | |
558 | ||
559 | ntlm_request->authchallenge = xstrndup(reply, NTLM_CHALLENGE_SZ + 5); | |
5d146f7d | 560 | } else if (strncasecmp(reply, "AF ", 3) == 0) { |
62e76326 | 561 | /* we're finished, release the helper */ |
562 | reply += 3; | |
563 | assert(r->auth_user_request != NULL); | |
564 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
565 | auth_user_request = r->auth_user_request; | |
82b045dc | 566 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
567 | assert(ntlm_request); | |
62e76326 | 568 | auth_user = auth_user_request->auth_user; |
569 | ntlm_user = static_cast<ntlm_user_t *>(auth_user_request->auth_user->scheme_data); | |
570 | assert(ntlm_user != NULL); | |
571 | result = S_HELPER_RELEASE; | |
572 | /* we only expect OK when finishing the handshake */ | |
573 | assert(ntlm_request->auth_state == AUTHENTICATE_STATE_RESPONSE); | |
574 | ntlm_user->username = xstrndup(reply, MAX_LOGIN_SZ); | |
575 | ntlm_request->authserver = NULL; | |
6437ac71 | 576 | #ifdef NTLM_FAIL_OPEN |
62e76326 | 577 | |
5d146f7d | 578 | } else if (strncasecmp(reply, "LD ", 3) == 0) { |
62e76326 | 579 | /* This is a variant of BH, which rather than deny access |
580 | * allows the user through. The helper is starved and then refreshed | |
581 | * via YR, all pending authentications are likely to fail also. | |
582 | * It is meant for those helpers which occasionally fail for | |
583 | * no reason at all (casus belli, NTLMSSP helper on NT domain, | |
584 | * failing about 1 auth out of 1k. | |
585 | * The code is a merge from the BH case with snippets of the AF | |
586 | * case */ | |
587 | /* AF code: mark user as authenticated */ | |
588 | reply += 3; | |
589 | assert(r->auth_user_request != NULL); | |
590 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
591 | auth_user_request = r->auth_user_request; | |
82b045dc | 592 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
593 | assert(ntlm_request); | |
62e76326 | 594 | auth_user = auth_user_request->auth_user; |
595 | ntlm_user = static_cast<ntlm_user_t *>(auth_user_request->auth_user->scheme_data); | |
596 | assert(ntlm_user != NULL); | |
597 | result = S_HELPER_RELEASE; | |
598 | /* we only expect LD when finishing the handshake */ | |
599 | assert(ntlm_request->auth_state == AUTHENTICATE_STATE_RESPONSE); | |
600 | ntlm_user->username = xstrndup(reply, MAX_LOGIN_SZ); | |
7928e475 | 601 | helperstate = static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(ntlm_request->authserver)); |
62e76326 | 602 | ntlm_request->authserver = NULL; |
603 | /* BH code: mark helper as broken */ | |
604 | /* mark it for starving */ | |
605 | helperstate->starve = 1; | |
6437ac71 | 606 | #endif |
62e76326 | 607 | |
5d146f7d | 608 | } else if (strncasecmp(reply, "NA ", 3) == 0) { |
62e76326 | 609 | /* TODO: only work with auth_user here if it exists */ |
610 | assert(r->auth_user_request != NULL); | |
611 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
612 | auth_user_request = r->auth_user_request; | |
613 | auth_user = auth_user_request->auth_user; | |
614 | assert(auth_user != NULL); | |
615 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); | |
82b045dc | 616 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
62e76326 | 617 | assert((ntlm_user != NULL) && (ntlm_request != NULL)); |
618 | /* todo: action of Negotiate state on error */ | |
619 | result = S_HELPER_RELEASE; /*some error has occured. no more requests */ | |
620 | ntlm_request->authserver = NULL; | |
621 | debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); | |
622 | ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; | |
623 | ||
624 | if ((t = strchr(reply, ' '))) /* strip after a space */ | |
625 | *t = '\0'; | |
5d146f7d | 626 | } else if (strncasecmp(reply, "NA", 2) == 0) { |
62e76326 | 627 | /* NTLM Helper protocol violation! */ |
628 | fatal("NTLM Helper returned invalid response \"NA\" - a error message MUST be attached\n"); | |
5d146f7d | 629 | } else if (strncasecmp(reply, "BH ", 3) == 0) { |
62e76326 | 630 | /* TODO kick off a refresh process. This can occur after a YR or after |
631 | * a KK. If after a YR release the helper and resubmit the request via | |
632 | * Authenticate NTLM start. | |
633 | * If after a KK deny the user's request w/ 407 and mark the helper as | |
634 | * Needing YR. */ | |
635 | assert(r->auth_user_request != NULL); | |
636 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
637 | auth_user_request = r->auth_user_request; | |
638 | auth_user = auth_user_request->auth_user; | |
639 | assert(auth_user != NULL); | |
640 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); | |
82b045dc | 641 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
62e76326 | 642 | assert((ntlm_user != NULL) && (ntlm_request != NULL)); |
82b045dc | 643 | /*some error has occured. no more requests for |
644 | * this helper */ | |
645 | result = S_HELPER_RELEASE; | |
62e76326 | 646 | assert(ntlm_request->authserver ? ntlm_request->authserver == lastserver : 1); |
647 | helperstate = static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(ntlm_request->authserver)); | |
648 | ntlm_request->authserver = NULL; | |
649 | ||
650 | if (ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE) { | |
651 | /* The helper broke on YR. It automatically | |
652 | * resets */ | |
653 | debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %p. Error returned '%s'\n", lastserver, reply); | |
654 | /* mark it for starving */ | |
655 | helperstate->starve = 1; | |
656 | /* resubmit the request. This helper is currently busy, so we will get | |
657 | * a different one. Our auth state stays the same */ | |
658 | authenticateNTLMStart(auth_user_request, r->handler, r->data); | |
659 | /* don't call the callback */ | |
660 | cbdataReferenceDone(r->data); | |
661 | authenticateStateFree(r); | |
662 | debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result); | |
663 | return result; | |
664 | } | |
665 | ||
666 | /* the helper broke on a KK */ | |
667 | /* first the standard KK stuff */ | |
668 | debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); | |
669 | ||
670 | if ((t = strchr(reply, ' '))) /* strip after a space */ | |
671 | *t = '\0'; | |
672 | ||
673 | /* now we mark the helper for resetting. */ | |
674 | helperstate->starve = 1; | |
675 | ||
676 | ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; | |
94439e4e | 677 | } else { |
62e76326 | 678 | /* TODO: only work with auth_user here if it exists */ |
679 | /* TODO: take the request state into consideration */ | |
680 | assert(r->auth_user_request != NULL); | |
681 | assert(r->auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
682 | auth_user_request = r->auth_user_request; | |
683 | auth_user = auth_user_request->auth_user; | |
684 | assert(auth_user != NULL); | |
685 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); | |
82b045dc | 686 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
62e76326 | 687 | assert((ntlm_user != NULL) && (ntlm_request != NULL)); |
688 | debug(29, 1) ("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply); | |
689 | /* **** NOTE THIS CODE IS EFFECTIVELY UNTESTED **** */ | |
690 | /* restart the authentication process */ | |
691 | ntlm_request->auth_state = AUTHENTICATE_STATE_NONE; | |
692 | assert(ntlm_request->authserver ? ntlm_request->authserver == lastserver : 1); | |
693 | ntlm_request->authserver = NULL; | |
94439e4e | 694 | } |
62e76326 | 695 | |
5d146f7d | 696 | r->handler(r->data, NULL); |
fa80a8ef | 697 | cbdataReferenceDone(r->data); |
94439e4e | 698 | authenticateStateFree(r); |
699 | debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result); | |
700 | return result; | |
701 | } | |
702 | ||
94439e4e | 703 | static void |
704 | authenticateNTLMStats(StoreEntry * sentry) | |
705 | { | |
706 | storeAppendPrintf(sentry, "NTLM Authenticator Statistics:\n"); | |
707 | helperStatefulStats(sentry, ntlmauthenticators); | |
708 | } | |
709 | ||
710 | /* is a particular challenge still valid ? */ | |
c78aa667 | 711 | static int |
94439e4e | 712 | authenticateNTLMValidChallenge(ntlm_helper_state_t * helperstate) |
713 | { | |
6437ac71 | 714 | debug(29, 9) ("authenticateNTLMValidChallenge: Challenge is %s\n", helperstate->challenge ? "Valid" : "Invalid"); |
62e76326 | 715 | |
94439e4e | 716 | if (helperstate->challenge == NULL) |
62e76326 | 717 | return 0; |
718 | ||
94439e4e | 719 | return 1; |
720 | } | |
721 | ||
722 | /* does our policy call for changing the challenge now? */ | |
c78aa667 | 723 | static int |
60d096f4 | 724 | authenticateNTLMChangeChallenge_p(ntlm_helper_state_t * helperstate) |
94439e4e | 725 | { |
726 | /* don't check for invalid challenges just for expiry choices */ | |
727 | /* this is needed because we have to starve the helper until all old | |
728 | * requests have been satisfied */ | |
62e76326 | 729 | |
6437ac71 | 730 | if (!helperstate->renewed) { |
62e76326 | 731 | /* first use, no challenge has been set. Without this check, it will |
732 | * loop forever */ | |
733 | debug(29, 5) ("authenticateNTLMChangeChallenge_p: first use\n"); | |
734 | return 0; | |
6437ac71 | 735 | } |
62e76326 | 736 | |
6437ac71 | 737 | if (helperstate->challengeuses > ntlmConfig->challengeuses) { |
62e76326 | 738 | debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge uses (%d) exceeded max uses (%d)\n", helperstate->challengeuses, ntlmConfig->challengeuses); |
739 | return 1; | |
6437ac71 | 740 | } |
62e76326 | 741 | |
6437ac71 | 742 | if (helperstate->renewed + ntlmConfig->challengelifetime < squid_curtime) { |
62e76326 | 743 | debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge exceeded max lifetime by %d seconds\n", (int) (squid_curtime - (helperstate->renewed + ntlmConfig->challengelifetime))); |
744 | return 1; | |
6437ac71 | 745 | } |
62e76326 | 746 | |
6437ac71 | 747 | debug(29, 9) ("Challenge is to be reused\n"); |
94439e4e | 748 | return 0; |
749 | } | |
750 | ||
751 | /* send the initial data to a stateful ntlm authenticator module */ | |
752 | static void | |
753 | authenticateNTLMStart(auth_user_request_t * auth_user_request, RH * handler, void *data) | |
754 | { | |
94439e4e | 755 | authenticateStateData *r = NULL; |
756 | helper_stateful_server *server; | |
757 | ntlm_helper_state_t *helperstate; | |
758 | char buf[8192]; | |
759 | char *sent_string = NULL; | |
760 | ntlm_user_t *ntlm_user; | |
761 | ntlm_request_t *ntlm_request; | |
762 | auth_user_t *auth_user; | |
763 | ||
764 | assert(auth_user_request); | |
765 | auth_user = auth_user_request->auth_user; | |
e6ccf245 | 766 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); |
82b045dc | 767 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
94439e4e | 768 | assert(ntlm_user); |
769 | assert(ntlm_request); | |
770 | assert(handler); | |
771 | assert(data); | |
9970d4b1 | 772 | assert(auth_user->auth_type == AUTH_NTLM); |
94439e4e | 773 | debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", ntlm_request->auth_state); |
62e76326 | 774 | |
94439e4e | 775 | switch (ntlm_request->auth_state) { |
62e76326 | 776 | |
94439e4e | 777 | case AUTHENTICATE_STATE_NEGOTIATE: |
62e76326 | 778 | sent_string = ntlm_request->ntlmnegotiate; |
779 | break; | |
780 | ||
94439e4e | 781 | case AUTHENTICATE_STATE_RESPONSE: |
62e76326 | 782 | sent_string = ntlm_request->ntlmauthenticate; |
783 | assert(ntlm_request->authserver); | |
784 | debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%p'.\n", ntlm_request->authserver); | |
785 | break; | |
786 | ||
94439e4e | 787 | default: |
62e76326 | 788 | fatal("Invalid authenticate state for NTLMStart"); |
94439e4e | 789 | } |
790 | ||
791 | while (!xisspace(*sent_string)) /*trim NTLM */ | |
62e76326 | 792 | sent_string++; |
94439e4e | 793 | |
794 | while (xisspace(*sent_string)) /*trim leading spaces */ | |
62e76326 | 795 | sent_string++; |
94439e4e | 796 | |
797 | debug(29, 9) ("authenticateNTLMStart: state '%d'\n", ntlm_request->auth_state); | |
62e76326 | 798 | |
94439e4e | 799 | debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string); |
62e76326 | 800 | |
94439e4e | 801 | if (ntlmConfig->authenticate == NULL) { |
62e76326 | 802 | debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string); |
803 | handler(data, NULL); | |
804 | return; | |
94439e4e | 805 | } |
62e76326 | 806 | |
94439e4e | 807 | /* this is ugly TODO: move the challenge generation routines to their own function and |
808 | * tidy the logic up to make use of the efficiency we now have */ | |
809 | switch (ntlm_request->auth_state) { | |
62e76326 | 810 | |
94439e4e | 811 | case AUTHENTICATE_STATE_NEGOTIATE: |
62e76326 | 812 | /* |
813 | * 1: get a helper server | |
814 | * 2: does it have a challenge? | |
815 | * 3: tell it to get a challenge, or give ntlmauthdone the challenge | |
816 | */ | |
817 | server = helperStatefulDefer(ntlmauthenticators); | |
818 | helperstate = server ? static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(server)) : NULL; | |
819 | ||
820 | while ((server != NULL) && authenticateNTLMChangeChallenge_p(helperstate)) { | |
821 | /* flag this helper for challenge changing */ | |
822 | helperstate->starve = 1; | |
823 | /* and release the deferred request */ | |
824 | helperStatefulReleaseServer(server); | |
825 | /* Get another deferrable server */ | |
826 | server = helperStatefulDefer(ntlmauthenticators); | |
827 | helperstate = server ? static_cast<ntlm_helper_state_t *>(helperStatefulServerGetData(server)) : NULL; | |
828 | } | |
829 | ||
830 | if (server == NULL) | |
831 | debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n"); | |
832 | ||
833 | ntlm_request->authserver = server; | |
834 | ||
835 | /* tell the log what helper we have been given */ | |
836 | debug(29, 9) ("authenticateNTLMStart: helper '%p' assigned\n", server); | |
837 | ||
838 | /* server and valid challenge? */ | |
839 | if ((server == NULL) || !authenticateNTLMValidChallenge(helperstate)) { | |
840 | /* No server, or server with invalid challenge */ | |
841 | r = cbdataAlloc(authenticateStateData); | |
842 | r->handler = handler; | |
843 | r->data = cbdataReference(data); | |
844 | r->auth_user_request = auth_user_request; | |
845 | ||
846 | if (server == NULL) { | |
847 | helperStatefulSubmit(ntlmauthenticators, NULL, authenticateNTLMHandleplaceholder, r, NULL); | |
848 | } else { | |
849 | /* Server with invalid challenge */ | |
850 | snprintf(buf, 8192, "YR\n"); | |
851 | helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authserver); | |
852 | } | |
853 | } else { | |
854 | /* (server != NULL and we have a valid challenge) */ | |
855 | /* TODO: turn the below into a function and call from here and handlereply */ | |
856 | /* increment the challenge uses */ | |
857 | helperstate->challengeuses++; | |
858 | /* assign the challenge */ | |
859 | ntlm_request->authchallenge = xstrndup(helperstate->challenge, NTLM_CHALLENGE_SZ + 5); | |
860 | /* we're not actually submitting a request, so we need to release the helper | |
861 | * should the connection close unexpectedly | |
862 | */ | |
863 | ntlm_request->authserver_deferred = 1; | |
864 | handler(data, NULL); | |
865 | } | |
866 | ||
867 | break; | |
868 | ||
94439e4e | 869 | case AUTHENTICATE_STATE_RESPONSE: |
62e76326 | 870 | r = cbdataAlloc(authenticateStateData); |
871 | r->handler = handler; | |
872 | r->data = cbdataReference(data); | |
873 | r->auth_user_request = auth_user_request; | |
874 | snprintf(buf, 8192, "KK %s\n", sent_string); | |
875 | /* getting rid of deferred request status */ | |
876 | ntlm_request->authserver_deferred = 0; | |
877 | helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, ntlm_request->authserver); | |
878 | debug(29, 9) ("authenticateNTLMstart: finished\n"); | |
879 | break; | |
880 | ||
94439e4e | 881 | default: |
62e76326 | 882 | fatal("Invalid authenticate state for NTLMStart"); |
94439e4e | 883 | } |
94439e4e | 884 | } |
885 | ||
886 | /* callback used by stateful helper routines */ | |
c78aa667 | 887 | static int |
94439e4e | 888 | authenticateNTLMHelperServerAvailable(void *data) |
889 | { | |
e6ccf245 | 890 | ntlm_helper_state_t *statedata = static_cast<ntlm_helper_state_t *>(data); |
62e76326 | 891 | |
94439e4e | 892 | if (statedata != NULL) { |
62e76326 | 893 | if (statedata->starve) { |
894 | debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n"); | |
895 | return 0; | |
896 | } else { | |
897 | debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n"); | |
898 | return 1; | |
899 | } | |
94439e4e | 900 | } |
62e76326 | 901 | |
94439e4e | 902 | debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n"); |
903 | return 0; | |
904 | } | |
905 | ||
c78aa667 | 906 | static void |
94439e4e | 907 | authenticateNTLMHelperServerOnEmpty(void *data) |
908 | { | |
e6ccf245 | 909 | ntlm_helper_state_t *statedata = static_cast<ntlm_helper_state_t *>(data); |
62e76326 | 910 | |
94439e4e | 911 | if (statedata == NULL) |
62e76326 | 912 | return; |
913 | ||
94439e4e | 914 | if (statedata->starve) { |
62e76326 | 915 | /* we have been starving the helper */ |
916 | debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n"); | |
917 | statedata->starve = 0; | |
918 | statedata->challengeuses = 0; | |
919 | statedata->renewed = 0; | |
920 | xfree(statedata->challenge); | |
921 | statedata->challenge = NULL; | |
94439e4e | 922 | } |
923 | } | |
924 | ||
925 | ||
926 | /* clear the NTLM helper of being reserved for future requests */ | |
c78aa667 | 927 | static void |
60d096f4 | 928 | authenticateNTLMReleaseServer(auth_user_request_t * auth_user_request) |
94439e4e | 929 | { |
930 | ntlm_request_t *ntlm_request; | |
931 | assert(auth_user_request->auth_user->auth_type == AUTH_NTLM); | |
82b045dc | 932 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
933 | assert (ntlm_request); | |
33272404 | 934 | debug(29, 9) ("authenticateNTLMReleaseServer: releasing server '%p'\n", ntlm_request->authserver); |
60d096f4 | 935 | helperStatefulReleaseServer(ntlm_request->authserver); |
936 | ntlm_request->authserver = NULL; | |
94439e4e | 937 | } |
938 | ||
939 | /* clear any connection related authentication details */ | |
c78aa667 | 940 | static void |
94439e4e | 941 | authenticateNTLMOnCloseConnection(ConnStateData * conn) |
942 | { | |
943 | ntlm_request_t *ntlm_request; | |
944 | assert(conn != NULL); | |
62e76326 | 945 | |
94439e4e | 946 | if (conn->auth_user_request != NULL) { |
82b045dc | 947 | ntlm_request = dynamic_cast< ntlm_request_t *>(conn->auth_user_request->state()); |
948 | assert (ntlm_request); | |
62e76326 | 949 | assert(ntlm_request->conn == conn); |
950 | ||
951 | if (ntlm_request->authserver != NULL && ntlm_request->authserver_deferred) | |
952 | authenticateNTLMReleaseServer(conn->auth_user_request); | |
953 | ||
954 | /* unlock the connection based lock */ | |
955 | debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n"); | |
956 | ||
3587fdd2 | 957 | /* This still breaks the abstraction, but is at least read only now. |
958 | * If needed, this could be ignored, as the conn deletion will also unlock | |
959 | * the auth user request. | |
960 | */ | |
62e76326 | 961 | authenticateAuthUserRequestUnlock(conn->auth_user_request); |
962 | ||
963 | conn->auth_user_request = NULL; | |
94439e4e | 964 | } |
965 | } | |
966 | ||
967 | /* authenticateUserUsername: return a pointer to the username in the */ | |
e6ccf245 | 968 | static const char * |
969 | authenticateNTLMUsername(auth_user_t const * auth_user) | |
94439e4e | 970 | { |
e6ccf245 | 971 | ntlm_user_t *ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); |
62e76326 | 972 | |
94439e4e | 973 | if (ntlm_user) |
62e76326 | 974 | return ntlm_user->username; |
975 | ||
94439e4e | 976 | return NULL; |
977 | } | |
978 | ||
60d096f4 | 979 | /* NTLMLastHeader: return a pointer to the last header used in authenticating |
980 | * the request/conneciton | |
981 | */ | |
c78aa667 | 982 | static const char * |
60d096f4 | 983 | NTLMLastHeader(auth_user_request_t * auth_user_request) |
984 | { | |
985 | ntlm_request_t *ntlm_request; | |
986 | assert(auth_user_request != NULL); | |
82b045dc | 987 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
988 | assert (ntlm_request); | |
60d096f4 | 989 | return ntlm_request->ntlmauthenticate; |
990 | } | |
94439e4e | 991 | |
992 | /* | |
993 | * Decode an NTLM [Proxy-]Auth string, placing the results in the passed | |
994 | * Auth_user structure. | |
995 | */ | |
996 | ||
c78aa667 | 997 | static void |
94439e4e | 998 | authenticateDecodeNTLMAuth(auth_user_request_t * auth_user_request, const char *proxy_auth) |
999 | { | |
1000 | dlink_node *node; | |
1001 | assert(auth_user_request->auth_user == NULL); | |
1002 | auth_user_request->auth_user = authenticateAuthUserNew("ntlm"); | |
1003 | auth_user_request->auth_user->auth_type = AUTH_NTLM; | |
1004 | auth_user_request->auth_user->scheme_data = memPoolAlloc(ntlm_user_pool); | |
82b045dc | 1005 | auth_user_request->state (new ntlm_request_t); |
94439e4e | 1006 | /* lock for the auth_user_request link */ |
1007 | authenticateAuthUserLock(auth_user_request->auth_user); | |
1008 | node = dlinkNodeNew(); | |
1009 | dlinkAdd(auth_user_request, node, &auth_user_request->auth_user->requests); | |
1010 | ||
1011 | /* all we have to do is identify that it's NTLM - the helper does the rest */ | |
1012 | debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n"); | |
1013 | return; | |
1014 | } | |
1015 | ||
c78aa667 | 1016 | static int |
94439e4e | 1017 | authenticateNTLMcmpUsername(ntlm_user_t * u1, ntlm_user_t * u2) |
1018 | { | |
1019 | return strcmp(u1->username, u2->username); | |
1020 | } | |
1021 | ||
60d096f4 | 1022 | |
1023 | /* there is a known race where a single client recieves the same challenge | |
1024 | * and sends the same response to squid on a single select cycle. | |
1025 | * Check for this and if found ignore the new link | |
1026 | */ | |
c78aa667 | 1027 | static void |
94439e4e | 1028 | authenticateProxyAuthCacheAddLink(const char *key, auth_user_t * auth_user) |
1029 | { | |
62e76326 | 1030 | |
e6ccf245 | 1031 | struct ProxyAuthCachePointer *proxy_auth_hash; |
60d096f4 | 1032 | dlink_node *node; |
94439e4e | 1033 | ntlm_user_t *ntlm_user; |
e6ccf245 | 1034 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); |
60d096f4 | 1035 | node = ntlm_user->proxy_auth_list.head; |
1036 | /* prevent duplicates */ | |
62e76326 | 1037 | |
60d096f4 | 1038 | while (node) { |
62e76326 | 1039 | |
1040 | if (!strcmp(key, (char const *)((struct ProxyAuthCachePointer *) node->data)->key)) | |
1041 | return; | |
1042 | ||
1043 | node = node->next; | |
60d096f4 | 1044 | } |
62e76326 | 1045 | |
e6ccf245 | 1046 | proxy_auth_hash = static_cast<ProxyAuthCachePointer *>(memPoolAlloc(ntlm_user_hash_pool)); |
94439e4e | 1047 | proxy_auth_hash->key = xstrdup(key); |
1048 | proxy_auth_hash->auth_user = auth_user; | |
6437ac71 | 1049 | dlinkAddTail(proxy_auth_hash, &proxy_auth_hash->link, &ntlm_user->proxy_auth_list); |
94439e4e | 1050 | hash_join(proxy_auth_cache, (hash_link *) proxy_auth_hash); |
1051 | } | |
1052 | ||
82b045dc | 1053 | int |
1054 | ntlm_request_t::authenticated() const | |
94439e4e | 1055 | { |
82b045dc | 1056 | if (auth_state == AUTHENTICATE_STATE_DONE) |
62e76326 | 1057 | return 1; |
1058 | ||
94439e4e | 1059 | debug(29, 9) ("User not fully authenticated.\n"); |
62e76326 | 1060 | |
94439e4e | 1061 | return 0; |
1062 | } | |
1063 | ||
82b045dc | 1064 | void |
190154cf | 1065 | ntlm_request_t::authenticate(HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type) |
82b045dc | 1066 | { |
1067 | fatal ("unusable"); | |
1068 | } | |
1069 | ||
94439e4e | 1070 | static void |
190154cf | 1071 | authenticateNTLMAuthenticateUser(auth_user_request_t * auth_user_request, HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type) |
94439e4e | 1072 | { |
1073 | const char *proxy_auth; | |
62e76326 | 1074 | |
e6ccf245 | 1075 | struct ProxyAuthCachePointer *proxy_auth_hash = NULL; |
1076 | auth_user_hash_pointer *usernamehash; | |
94439e4e | 1077 | auth_user_t *auth_user; |
1078 | ntlm_request_t *ntlm_request; | |
1079 | ntlm_user_t *ntlm_user; | |
1080 | LOCAL_ARRAY(char, ntlmhash, NTLM_CHALLENGE_SZ * 2); | |
1081 | /* get header */ | |
1082 | proxy_auth = httpHeaderGetStr(&request->header, type); | |
1083 | ||
1084 | auth_user = auth_user_request->auth_user; | |
1085 | assert(auth_user); | |
1086 | assert(auth_user->auth_type == AUTH_NTLM); | |
1087 | assert(auth_user->scheme_data != NULL); | |
e6ccf245 | 1088 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); |
82b045dc | 1089 | ntlm_request = dynamic_cast< ntlm_request_t *>(auth_user_request->state()); |
1090 | assert (ntlm_request); | |
3a73fe76 | 1091 | /* Check that we are in the client side, where we can generate |
1092 | * auth challenges */ | |
62e76326 | 1093 | |
a2ac85d9 | 1094 | if (conn.getRaw() == NULL) { |
62e76326 | 1095 | ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; |
1096 | debug(29, 1) ("authenticateNTLMAuthenticateUser: attempt to perform authentication without a connection!\n"); | |
1097 | return; | |
3a73fe76 | 1098 | } |
62e76326 | 1099 | |
94439e4e | 1100 | switch (ntlm_request->auth_state) { |
62e76326 | 1101 | |
94439e4e | 1102 | case AUTHENTICATE_STATE_NONE: |
62e76326 | 1103 | /* we've recieved a negotiate request. pass to a helper */ |
1104 | debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n", proxy_auth); | |
1105 | ntlm_request->auth_state = AUTHENTICATE_STATE_NEGOTIATE; | |
1106 | ntlm_request->ntlmnegotiate = xstrndup(proxy_auth, NTLM_CHALLENGE_SZ + 5); | |
1107 | conn->auth_type = AUTH_NTLM; | |
1108 | conn->auth_user_request = auth_user_request; | |
1109 | ntlm_request->conn = conn; | |
1110 | /* and lock for the connection duration */ | |
1111 | debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n"); | |
1112 | authenticateAuthUserRequestLock(auth_user_request); | |
1113 | return; | |
1114 | break; | |
1115 | ||
94439e4e | 1116 | case AUTHENTICATE_STATE_NEGOTIATE: |
62e76326 | 1117 | ntlm_request->auth_state = AUTHENTICATE_STATE_CHALLENGE; |
1118 | /* We _MUST_ have the auth challenge by now */ | |
1119 | assert(ntlm_request->authchallenge); | |
1120 | return; | |
1121 | break; | |
1122 | ||
94439e4e | 1123 | case AUTHENTICATE_STATE_CHALLENGE: |
62e76326 | 1124 | /* we should have recieved a NTLM challenge. pass it to the same |
1125 | * helper process */ | |
1126 | debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth); | |
1127 | /* do a cache lookup here. If it matches it's a successful ntlm | |
1128 | * challenge - release the helper and use the existing auth_user | |
1129 | * details. */ | |
1130 | ||
1131 | if (strncmp("NTLM ", proxy_auth, 5) == 0) { | |
1132 | ntlm_request->ntlmauthenticate = xstrdup(proxy_auth); | |
1133 | } else { | |
1134 | fatal("Incorrect scheme in auth header\n"); | |
1135 | /* TODO: more fault tolerance.. reset the auth scheme here */ | |
1136 | } | |
1137 | ||
1138 | /* cache entries have authenticateauthheaderchallengestring */ | |
1139 | snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s", | |
1140 | ntlm_request->ntlmauthenticate, | |
1141 | ntlm_request->authchallenge); | |
1142 | ||
1143 | /* see if we already know this user's authenticate */ | |
1144 | debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash); | |
1145 | ||
1146 | assert(proxy_auth_cache != NULL); | |
1147 | ||
1148 | proxy_auth_hash = static_cast<ProxyAuthCachePointer *>(hash_lookup(proxy_auth_cache, ntlmhash)); | |
1149 | ||
1150 | if (!proxy_auth_hash) { /* not in the hash table */ | |
1151 | debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n"); | |
1152 | ntlm_request->auth_state = AUTHENTICATE_STATE_RESPONSE; | |
1153 | /* verify with the ntlm helper */ | |
1154 | } else { | |
1155 | debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n"); | |
1156 | /* throw away the temporary entry */ | |
1157 | ntlm_request->authserver_deferred = 0; | |
1158 | authenticateNTLMReleaseServer(auth_user_request); | |
1159 | authenticateAuthUserMerge(auth_user, proxy_auth_hash->auth_user); | |
1160 | auth_user = proxy_auth_hash->auth_user; | |
1161 | auth_user_request->auth_user = auth_user; | |
1162 | ntlm_request->auth_state = AUTHENTICATE_STATE_DONE; | |
1163 | /* we found one */ | |
1164 | debug(29, 9) ("found matching cache entry\n"); | |
1165 | assert(auth_user->auth_type == AUTH_NTLM); | |
1166 | /* get the existing entries details */ | |
1167 | ntlm_user = static_cast<ntlm_user_t *>(auth_user->scheme_data); | |
1168 | debug(29, 9) ("Username to be used is %s\n", ntlm_user->username); | |
1169 | /* on ntlm auth we do not unlock the auth_user until the | |
1170 | * connection is dropped. Thank MS for this quirk */ | |
1171 | auth_user->expiretime = current_time.tv_sec; | |
1172 | } | |
1173 | ||
1174 | return; | |
1175 | break; | |
1176 | ||
94439e4e | 1177 | case AUTHENTICATE_STATE_RESPONSE: |
62e76326 | 1178 | /* auth-challenge pair cache miss. We've just got the response from the helper */ |
1179 | /*add to cache and let them through */ | |
1180 | ntlm_request->auth_state = AUTHENTICATE_STATE_DONE; | |
1181 | /* this connection is authenticated */ | |
1182 | debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n", | |
1183 | ntlm_request->authchallenge, | |
1184 | ntlm_request->ntlmauthenticate, | |
1185 | ntlm_user->username); | |
1186 | /* cache entries have authenticateauthheaderchallengestring */ | |
1187 | snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s", | |
1188 | ntlm_request->ntlmauthenticate, | |
1189 | ntlm_request->authchallenge); | |
1190 | /* see if this is an existing user with a different proxy_auth | |
1191 | * string */ | |
1192 | ||
1193 | if ((usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, ntlm_user->username))) | |
1194 | ) { | |
1195 | while ((authUserHashPointerUser(usernamehash)->auth_type != auth_user->auth_type) && (usernamehash->next) && !authenticateNTLMcmpUsername(static_cast<ntlm_user_t *>(authUserHashPointerUser(usernamehash)->scheme_data), ntlm_user) | |
1196 | ) | |
1197 | usernamehash = static_cast<AuthUserHashPointer*>(usernamehash->next); | |
1198 | if (authUserHashPointerUser(usernamehash)->auth_type == auth_user->auth_type) { | |
1199 | /* | |
1200 | * add another link from the new proxy_auth to the | |
1201 | * auth_user structure and update the information */ | |
1202 | assert(proxy_auth_hash == NULL); | |
1203 | authenticateProxyAuthCacheAddLink(ntlmhash, authUserHashPointerUser(usernamehash)); | |
1204 | /* we can't seamlessly recheck the username due to the | |
1205 | * challenge nature of the protocol. Just free the | |
1206 | * temporary auth_user */ | |
1207 | authenticateAuthUserMerge(auth_user, authUserHashPointerUser(usernamehash)); | |
1208 | auth_user = authUserHashPointerUser(usernamehash); | |
1209 | auth_user_request->auth_user = auth_user; | |
1210 | } | |
1211 | } else { | |
1212 | /* store user in hash's */ | |
1213 | authenticateUserNameCacheAdd(auth_user); | |
1214 | authenticateProxyAuthCacheAddLink(ntlmhash, auth_user); | |
1215 | } | |
1216 | ||
1217 | /* set these to now because this is either a new login from an | |
1218 | * existing user or a new user */ | |
1219 | auth_user->expiretime = current_time.tv_sec; | |
1220 | return; | |
1221 | break; | |
1222 | ||
94439e4e | 1223 | case AUTHENTICATE_STATE_DONE: |
62e76326 | 1224 | fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n"); |
1225 | break; | |
1226 | ||
3c641669 | 1227 | case AUTHENTICATE_STATE_FAILED: |
62e76326 | 1228 | /* we've failed somewhere in authentication */ |
1229 | debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm failed. %s\n", proxy_auth); | |
1230 | return; | |
94439e4e | 1231 | } |
62e76326 | 1232 | |
94439e4e | 1233 | return; |
1234 | } | |
82b045dc | 1235 | |
bf5113eb | 1236 | MemPool (*ntlm_request_t::Pool)(NULL); |
82b045dc | 1237 | void * |
1238 | ntlm_request_t::operator new (size_t byteCount) | |
1239 | { | |
1240 | /* derived classes with different sizes must implement their own new */ | |
1241 | assert (byteCount == sizeof (ntlm_request_t)); | |
1242 | ||
1243 | if (!Pool) | |
1244 | Pool = memPoolCreate("ntlm_request_t", sizeof (ntlm_request_t)); | |
1245 | ||
1246 | return memPoolAlloc(Pool); | |
1247 | } | |
1248 | ||
1249 | void | |
1250 | ntlm_request_t::operator delete (void *address) | |
1251 | { | |
1252 | memPoolFree (Pool, address); | |
1253 | } |