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