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