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