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