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