]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
f7f3304a | 9 | #include "squid.h" |
d4806c91 | 10 | #include "AccessLogEntry.h" |
fde785ee | 11 | #include "auth/CredentialsCache.h" |
12daeef6 | 12 | #include "auth/ntlm/Config.h" |
33e622d7 | 13 | #include "auth/ntlm/User.h" |
616cfc4c | 14 | #include "auth/ntlm/UserRequest.h" |
d232141d | 15 | #include "auth/State.h" |
928f3421 | 16 | #include "cbdata.h" |
582c2af2 | 17 | #include "client_side.h" |
ed6e9fb9 | 18 | #include "fatal.h" |
86c63190 | 19 | #include "format/Format.h" |
582c2af2 | 20 | #include "globals.h" |
24438ec5 AJ |
21 | #include "helper.h" |
22 | #include "helper/Reply.h" | |
d3dddfb5 | 23 | #include "http/Stream.h" |
d4806c91 | 24 | #include "HttpMsg.h" |
928f3421 | 25 | #include "HttpRequest.h" |
d4806c91 | 26 | #include "MemBuf.h" |
928f3421 AJ |
27 | #include "SquidTime.h" |
28 | ||
d59e4742 FC |
29 | Auth::Ntlm::UserRequest::UserRequest() : |
30 | authserver(nullptr), | |
31 | server_blob(nullptr), | |
32 | client_blob(nullptr), | |
33 | waiting(0), | |
34 | request(nullptr) | |
35 | {} | |
928f3421 | 36 | |
c7baff40 | 37 | Auth::Ntlm::UserRequest::~UserRequest() |
928f3421 | 38 | { |
8bf217bd | 39 | assert(LockCount()==0); |
928f3421 AJ |
40 | safe_free(server_blob); |
41 | safe_free(client_blob); | |
42 | ||
43 | releaseAuthServer(); | |
44 | ||
45 | if (request) { | |
46 | HTTPMSGUNLOCK(request); | |
47 | request = NULL; | |
48 | } | |
49 | } | |
50 | ||
51 | const char * | |
c7baff40 | 52 | Auth::Ntlm::UserRequest::connLastHeader() |
928f3421 AJ |
53 | { |
54 | return NULL; | |
55 | } | |
56 | ||
7afc3bf2 | 57 | int |
c7baff40 | 58 | Auth::Ntlm::UserRequest::authenticated() const |
7afc3bf2 AJ |
59 | { |
60 | if (user() != NULL && user()->credentials() == Auth::Ok) { | |
61 | debugs(29, 9, HERE << "user authenticated."); | |
62 | return 1; | |
63 | } | |
64 | ||
65 | debugs(29, 9, HERE << "user not fully authenticated."); | |
66 | return 0; | |
67 | } | |
68 | ||
d4806c91 CT |
69 | const char * |
70 | Auth::Ntlm::UserRequest::credentialsStr() | |
71 | { | |
72 | static char buf[MAX_AUTHTOKEN_LEN]; | |
d0873e0c | 73 | int printResult; |
d4806c91 | 74 | if (user()->credentials() == Auth::Pending) { |
d0873e0c | 75 | printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); |
d4806c91 | 76 | } else { |
d0873e0c | 77 | printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob); |
d4806c91 | 78 | } |
d0873e0c CT |
79 | |
80 | // truncation is OK because we are used only for logging | |
81 | if (printResult < 0) { | |
82 | debugs(29, 2, "Can not build ntlm authentication credentials."); | |
83 | buf[0] = '\0'; | |
84 | } else if (printResult >= (int)sizeof(buf)) | |
85 | debugs(29, 2, "Ntlm authentication credentials truncated."); | |
86 | ||
d4806c91 CT |
87 | return buf; |
88 | } | |
89 | ||
51a3dd58 | 90 | Auth::Direction |
c7baff40 | 91 | Auth::Ntlm::UserRequest::module_direction() |
928f3421 | 92 | { |
c7baff40 | 93 | /* null auth_user is checked for by Auth::UserRequest::direction() */ |
928f3421 AJ |
94 | |
95 | if (waiting || client_blob) | |
51a3dd58 | 96 | return Auth::CRED_LOOKUP; /* need helper response to continue */ |
928f3421 | 97 | |
616cfc4c | 98 | if (user()->auth_type != Auth::AUTH_NTLM) |
51a3dd58 | 99 | return Auth::CRED_ERROR; |
928f3421 | 100 | |
d232141d | 101 | switch (user()->credentials()) { |
928f3421 | 102 | |
d87154ee | 103 | case Auth::Handshake: |
928f3421 | 104 | assert(server_blob); |
7afc3bf2 | 105 | return Auth::CRED_CHALLENGE; |
928f3421 | 106 | |
d87154ee | 107 | case Auth::Ok: |
51a3dd58 | 108 | return Auth::CRED_VALID; |
928f3421 | 109 | |
d87154ee | 110 | case Auth::Failed: |
7afc3bf2 | 111 | return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE? |
928f3421 | 112 | |
d232141d AJ |
113 | default: |
114 | debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication in unexpected state: " << user()->credentials()); | |
51a3dd58 | 115 | return Auth::CRED_ERROR; |
d232141d | 116 | } |
928f3421 AJ |
117 | } |
118 | ||
928f3421 | 119 | void |
ced8def3 | 120 | Auth::Ntlm::UserRequest::startHelperLookup(HttpRequest *, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) |
928f3421 | 121 | { |
7afc3bf2 | 122 | static char buf[MAX_AUTHTOKEN_LEN]; |
928f3421 AJ |
123 | |
124 | assert(data); | |
125 | assert(handler); | |
928f3421 | 126 | |
dc79fed8 | 127 | if (static_cast<Auth::Ntlm::Config*>(Auth::SchemeConfig::Find("ntlm"))->authenticateProgram == NULL) { |
d232141d | 128 | debugs(29, DBG_CRITICAL, "ERROR: NTLM Start: no NTLM program configured."); |
4c535e87 | 129 | handler(data); |
ec5858ff | 130 | return; |
928f3421 AJ |
131 | } |
132 | ||
7afc3bf2 AJ |
133 | debugs(29, 8, HERE << "credentials state is '" << user()->credentials() << "'"); |
134 | ||
d4806c91 | 135 | const char *keyExtras = helperRequestKeyExtras(request, al); |
d0873e0c | 136 | int printResult = 0; |
d87154ee | 137 | if (user()->credentials() == Auth::Pending) { |
d4806c91 | 138 | if (keyExtras) |
d0873e0c | 139 | printResult = snprintf(buf, sizeof(buf), "YR %s %s\n", client_blob, keyExtras); |
d4806c91 | 140 | else |
d0873e0c | 141 | printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? |
928f3421 | 142 | } else { |
d4806c91 | 143 | if (keyExtras) |
d0873e0c | 144 | printResult = snprintf(buf, sizeof(buf), "KK %s %s\n", client_blob, keyExtras); |
d4806c91 | 145 | else |
d0873e0c | 146 | printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob); |
928f3421 | 147 | } |
928f3421 AJ |
148 | waiting = 1; |
149 | ||
d0873e0c CT |
150 | if (printResult < 0 || printResult >= (int)sizeof(buf)) { |
151 | if (printResult < 0) | |
152 | debugs(29, DBG_CRITICAL, "ERROR: Can not build ntlm authentication helper request"); | |
153 | else | |
154 | debugs(29, DBG_CRITICAL, "ERROR: Ntlm authentication helper request too big for the " << sizeof(buf) << "-byte buffer."); | |
155 | handler(data); | |
156 | return; | |
157 | } | |
158 | ||
928f3421 | 159 | safe_free(client_blob); |
c7baff40 | 160 | helperStatefulSubmit(ntlmauthenticators, buf, Auth::Ntlm::UserRequest::HandleReply, |
1c756645 | 161 | new Auth::StateData(this, handler, data), authserver); |
928f3421 AJ |
162 | } |
163 | ||
164 | /** | |
165 | * Atomic action: properly release the NTLM auth helpers which may have been reserved | |
166 | * for this request connections use. | |
167 | */ | |
168 | void | |
c7baff40 | 169 | Auth::Ntlm::UserRequest::releaseAuthServer() |
928f3421 AJ |
170 | { |
171 | if (authserver) { | |
172 | debugs(29, 6, HERE << "releasing NTLM auth server '" << authserver << "'"); | |
173 | helperStatefulReleaseServer(authserver); | |
174 | authserver = NULL; | |
175 | } else | |
176 | debugs(29, 6, HERE << "No NTLM auth server to release."); | |
177 | } | |
178 | ||
928f3421 | 179 | void |
789217a2 | 180 | Auth::Ntlm::UserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, Http::HdrType type) |
928f3421 | 181 | { |
928f3421 AJ |
182 | /* Check that we are in the client side, where we can generate |
183 | * auth challenges */ | |
184 | ||
185 | if (conn == NULL || !cbdataReferenceValid(conn)) { | |
d87154ee | 186 | user()->credentials(Auth::Failed); |
7afc3bf2 | 187 | debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication attempt to perform authentication without a connection!"); |
928f3421 AJ |
188 | return; |
189 | } | |
190 | ||
191 | if (waiting) { | |
7afc3bf2 | 192 | debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication waiting for helper reply!"); |
928f3421 AJ |
193 | return; |
194 | } | |
195 | ||
196 | if (server_blob) { | |
7afc3bf2 | 197 | debugs(29, 2, HERE << "need to challenge client '" << server_blob << "'!"); |
928f3421 AJ |
198 | return; |
199 | } | |
200 | ||
201 | /* get header */ | |
7afc3bf2 | 202 | const char *proxy_auth = aRequest->header.getStr(type); |
928f3421 AJ |
203 | |
204 | /* locate second word */ | |
7afc3bf2 | 205 | const char *blob = proxy_auth; |
928f3421 AJ |
206 | |
207 | /* if proxy_auth is actually NULL, we'd better not manipulate it. */ | |
208 | if (blob) { | |
209 | while (xisspace(*blob) && *blob) | |
742a021b | 210 | ++blob; |
928f3421 AJ |
211 | |
212 | while (!xisspace(*blob) && *blob) | |
742a021b | 213 | ++blob; |
928f3421 AJ |
214 | |
215 | while (xisspace(*blob) && *blob) | |
742a021b | 216 | ++blob; |
928f3421 AJ |
217 | } |
218 | ||
d232141d | 219 | switch (user()->credentials()) { |
928f3421 | 220 | |
d87154ee | 221 | case Auth::Unchecked: |
928f3421 | 222 | /* we've received a ntlm request. pass to a helper */ |
7afc3bf2 | 223 | debugs(29, 9, HERE << "auth state ntlm none. Received blob: '" << proxy_auth << "'"); |
d87154ee | 224 | user()->credentials(Auth::Pending); |
928f3421 AJ |
225 | safe_free(client_blob); |
226 | client_blob=xstrdup(blob); | |
cc1e110a AJ |
227 | assert(conn->getAuth() == NULL); |
228 | conn->setAuth(this, "new NTLM handshake request"); | |
928f3421 AJ |
229 | request = aRequest; |
230 | HTTPMSGLOCK(request); | |
231 | break; | |
232 | ||
d87154ee | 233 | case Auth::Pending: |
e0236918 | 234 | debugs(29, DBG_IMPORTANT, HERE << "need to ask helper"); |
928f3421 AJ |
235 | break; |
236 | ||
d87154ee | 237 | case Auth::Handshake: |
928f3421 AJ |
238 | /* we should have received a blob from the client. Hand it off to |
239 | * some helper */ | |
240 | safe_free(client_blob); | |
7afc3bf2 | 241 | client_blob = xstrdup(blob); |
928f3421 AJ |
242 | if (request) |
243 | HTTPMSGUNLOCK(request); | |
244 | request = aRequest; | |
245 | HTTPMSGLOCK(request); | |
246 | break; | |
247 | ||
d87154ee | 248 | case Auth::Ok: |
c7baff40 | 249 | fatal("Auth::Ntlm::UserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n"); |
928f3421 AJ |
250 | break; |
251 | ||
d87154ee | 252 | case Auth::Failed: |
928f3421 | 253 | /* we've failed somewhere in authentication */ |
7afc3bf2 | 254 | debugs(29, 9, HERE << "auth state ntlm failed. " << proxy_auth); |
928f3421 AJ |
255 | break; |
256 | } | |
257 | } | |
258 | ||
259 | void | |
24438ec5 | 260 | Auth::Ntlm::UserRequest::HandleReply(void *data, const Helper::Reply &reply) |
928f3421 | 261 | { |
1c756645 | 262 | Auth::StateData *r = static_cast<Auth::StateData *>(data); |
928f3421 | 263 | |
0272dd08 | 264 | debugs(29, 8, HERE << "helper: '" << reply.whichServer << "' sent us reply=" << reply); |
928f3421 | 265 | |
1c756645 | 266 | if (!cbdataReferenceValid(r->data)) { |
0272dd08 | 267 | debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication invalid callback data. helper '" << reply.whichServer << "'."); |
1c756645 | 268 | delete r; |
928f3421 AJ |
269 | return; |
270 | } | |
271 | ||
c7baff40 | 272 | Auth::UserRequest::Pointer auth_user_request = r->auth_user_request; |
928f3421 AJ |
273 | assert(auth_user_request != NULL); |
274 | ||
71e7400c AJ |
275 | // add new helper kv-pair notes to the credentials object |
276 | // so that any transaction using those credentials can access them | |
277 | auth_user_request->user()->notes.appendNewOnly(&reply.notes); | |
c10ebce8 AJ |
278 | // remove any private credentials detail which got added. |
279 | auth_user_request->user()->notes.remove("token"); | |
71e7400c | 280 | |
c7baff40 | 281 | Auth::Ntlm::UserRequest *lm_request = dynamic_cast<Auth::Ntlm::UserRequest *>(auth_user_request.getRaw()); |
7afc3bf2 AJ |
282 | assert(lm_request != NULL); |
283 | assert(lm_request->waiting); | |
56a49fda | 284 | |
7afc3bf2 AJ |
285 | lm_request->waiting = 0; |
286 | safe_free(lm_request->client_blob); | |
928f3421 | 287 | |
7afc3bf2 AJ |
288 | assert(auth_user_request->user() != NULL); |
289 | assert(auth_user_request->user()->auth_type == Auth::AUTH_NTLM); | |
290 | ||
291 | if (lm_request->authserver == NULL) | |
0272dd08 | 292 | lm_request->authserver = reply.whichServer.get(); // XXX: no locking? |
928f3421 | 293 | else |
e166785a | 294 | assert(reply.whichServer == lm_request->authserver); |
928f3421 | 295 | |
dacb64b9 | 296 | switch (reply.result) { |
2428ce02 | 297 | case Helper::TT: |
928f3421 | 298 | /* we have been given a blob to send to the client */ |
7afc3bf2 | 299 | safe_free(lm_request->server_blob); |
e857372a | 300 | lm_request->request->flags.mustKeepalive = true; |
450fe1cb | 301 | if (lm_request->request->flags.proxyKeepalive) { |
cf9f0261 CT |
302 | const char *serverBlob = reply.notes.findFirst("token"); |
303 | lm_request->server_blob = xstrdup(serverBlob); | |
7afc3bf2 | 304 | auth_user_request->user()->credentials(Auth::Handshake); |
928f3421 | 305 | auth_user_request->denyMessage("Authentication in progress"); |
cf9f0261 | 306 | debugs(29, 4, HERE << "Need to challenge the client with a server token: '" << serverBlob << "'"); |
928f3421 | 307 | } else { |
7afc3bf2 | 308 | auth_user_request->user()->credentials(Auth::Failed); |
928f3421 AJ |
309 | auth_user_request->denyMessage("NTLM authentication requires a persistent connection"); |
310 | } | |
0272dd08 AJ |
311 | break; |
312 | ||
2428ce02 | 313 | case Helper::Okay: { |
928f3421 | 314 | /* we're finished, release the helper */ |
cf9f0261 | 315 | const char *userLabel = reply.notes.findFirst("user"); |
10f8979e AJ |
316 | if (!userLabel) { |
317 | auth_user_request->user()->credentials(Auth::Failed); | |
318 | safe_free(lm_request->server_blob); | |
319 | lm_request->releaseAuthServer(); | |
320 | debugs(29, DBG_CRITICAL, "ERROR: NTLM Authentication helper returned no username. Result: " << reply); | |
321 | break; | |
322 | } | |
cf9f0261 | 323 | auth_user_request->user()->username(userLabel); |
928f3421 | 324 | auth_user_request->denyMessage("Login successful"); |
7afc3bf2 AJ |
325 | safe_free(lm_request->server_blob); |
326 | lm_request->releaseAuthServer(); | |
928f3421 | 327 | |
cf9f0261 | 328 | debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << userLabel << "'"); |
928f3421 | 329 | /* connection is authenticated */ |
7afc3bf2 | 330 | debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username()); |
c10ebce8 | 331 | /* see if this is an existing user */ |
33e622d7 | 332 | auto local_auth_user = lm_request->user(); |
02aeb7ef | 333 | auto cached_user = Auth::Ntlm::User::Cache()->lookup(auth_user_request->user()->userKey()); |
33e622d7 FC |
334 | if (!cached_user) { |
335 | local_auth_user->addToNameCache(); | |
336 | } else { | |
928f3421 AJ |
337 | /* we can't seamlessly recheck the username due to the |
338 | * challenge-response nature of the protocol. | |
58e94342 AJ |
339 | * Just free the temporary auth_user after merging as |
340 | * much of it new state into the existing one as possible */ | |
33e622d7 | 341 | cached_user->absorb(local_auth_user); |
7afc3bf2 | 342 | /* from here on we are working with the original cached credentials. */ |
33e622d7 | 343 | local_auth_user = cached_user; |
58e94342 | 344 | auth_user_request->user(local_auth_user); |
928f3421 AJ |
345 | } |
346 | /* set these to now because this is either a new login from an | |
347 | * existing user or a new user */ | |
348 | local_auth_user->expiretime = current_time.tv_sec; | |
7afc3bf2 | 349 | auth_user_request->user()->credentials(Auth::Ok); |
7bbefa01 | 350 | debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << auth_user_request->user()->username() << "'"); |
0272dd08 | 351 | } |
dacb64b9 | 352 | break; |
7afc3bf2 | 353 | |
2428ce02 | 354 | case Helper::Error: { |
928f3421 | 355 | /* authentication failure (wrong password, etc.) */ |
cf9f0261 | 356 | const char *errNote = reply.notes.find("message"); |
7bbefa01 | 357 | if (errNote != NULL) |
cf9f0261 | 358 | auth_user_request->denyMessage(errNote); |
7bbefa01 AJ |
359 | else |
360 | auth_user_request->denyMessage("NTLM Authentication denied with no reason given"); | |
7afc3bf2 AJ |
361 | auth_user_request->user()->credentials(Auth::Failed); |
362 | safe_free(lm_request->server_blob); | |
363 | lm_request->releaseAuthServer(); | |
87a122b0 | 364 | debugs(29, 4, "Failed validating user via NTLM. Result: " << reply); |
7bbefa01 AJ |
365 | } |
366 | break; | |
0272dd08 | 367 | |
2428ce02 | 368 | case Helper::Unknown: |
0272dd08 | 369 | debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication Helper '" << reply.whichServer << "' crashed!."); |
f53969cc | 370 | /* continue to the next case */ |
0272dd08 | 371 | |
32fd6d8a | 372 | case Helper::TimedOut: |
2428ce02 | 373 | case Helper::BrokenHelper: { |
928f3421 AJ |
374 | /* TODO kick off a refresh process. This can occur after a YR or after |
375 | * a KK. If after a YR release the helper and resubmit the request via | |
376 | * Authenticate NTLM start. | |
377 | * If after a KK deny the user's request w/ 407 and mark the helper as | |
378 | * Needing YR. */ | |
cf9f0261 | 379 | const char *errNote = reply.notes.find("message"); |
2428ce02 | 380 | if (reply.result == Helper::Unknown) |
7bbefa01 AJ |
381 | auth_user_request->denyMessage("Internal Error"); |
382 | else if (errNote != NULL) | |
cf9f0261 | 383 | auth_user_request->denyMessage(errNote); |
7bbefa01 AJ |
384 | else |
385 | auth_user_request->denyMessage("NTLM Authentication failed with no reason given"); | |
d87154ee | 386 | auth_user_request->user()->credentials(Auth::Failed); |
7afc3bf2 AJ |
387 | safe_free(lm_request->server_blob); |
388 | lm_request->releaseAuthServer(); | |
87a122b0 | 389 | debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication validating user. Result: " << reply); |
7bbefa01 AJ |
390 | } |
391 | break; | |
928f3421 AJ |
392 | } |
393 | ||
7afc3bf2 AJ |
394 | if (lm_request->request) { |
395 | HTTPMSGUNLOCK(lm_request->request); | |
396 | lm_request->request = NULL; | |
928f3421 | 397 | } |
4c535e87 | 398 | r->handler(r->data); |
1c756645 | 399 | delete r; |
928f3421 | 400 | } |
f53969cc | 401 |