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