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