2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
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.
10 #include "AccessLogEntry.h"
11 #include "auth/negotiate/Config.h"
12 #include "auth/negotiate/UserRequest.h"
13 #include "auth/State.h"
14 #include "auth/User.h"
15 #include "client_side.h"
17 #include "format/Format.h"
20 #include "helper/Reply.h"
21 #include "HttpHeaderTools.h"
22 #include "HttpReply.h"
23 #include "HttpRequest.h"
25 #include "SquidTime.h"
27 Auth::Negotiate::UserRequest::UserRequest()
36 Auth::Negotiate::UserRequest::~UserRequest()
38 assert(LockCount()==0);
39 safe_free(server_blob
);
40 safe_free(client_blob
);
45 HTTPMSGUNLOCK(request
);
51 Auth::Negotiate::UserRequest::connLastHeader()
57 Auth::Negotiate::UserRequest::authenticated() const
59 if (user() != NULL
&& user()->credentials() == Auth::Ok
) {
60 debugs(29, 9, HERE
<< "user authenticated.");
64 debugs(29, 9, HERE
<< "user not fully authenticated.");
69 Auth::Negotiate::UserRequest::credentialsStr()
71 static char buf
[MAX_AUTHTOKEN_LEN
];
72 if (user()->credentials() == Auth::Pending
) {
73 snprintf(buf
, sizeof(buf
), "YR %s\n", client_blob
); //CHECKME: can ever client_blob be 0 here?
75 snprintf(buf
, sizeof(buf
), "KK %s\n", client_blob
);
81 Auth::Negotiate::UserRequest::module_direction()
83 /* null auth_user is checked for by Auth::UserRequest::direction() */
85 if (waiting
|| client_blob
)
86 return Auth::CRED_LOOKUP
; /* need helper response to continue */
88 if (user()->auth_type
!= Auth::AUTH_NEGOTIATE
)
89 return Auth::CRED_ERROR
;
91 switch (user()->credentials()) {
95 return Auth::CRED_CHALLENGE
;
98 return Auth::CRED_VALID
;
101 return Auth::CRED_ERROR
; // XXX: really? not VALID or CHALLENGE?
104 debugs(29, DBG_IMPORTANT
, "WARNING: Negotiate Authentication in unexpected state: " << user()->credentials());
105 return Auth::CRED_ERROR
;
110 Auth::Negotiate::UserRequest::startHelperLookup(HttpRequest
*req
, AccessLogEntry::Pointer
&al
, AUTHCB
* handler
, void *data
)
112 static char buf
[MAX_AUTHTOKEN_LEN
];
117 assert(user() != NULL
);
118 assert(user()->auth_type
== Auth::AUTH_NEGOTIATE
);
120 if (static_cast<Auth::Negotiate::Config
*>(Auth::Config::Find("negotiate"))->authenticateProgram
== NULL
) {
121 debugs(29, DBG_CRITICAL
, "ERROR: No Negotiate authentication program configured.");
126 debugs(29, 8, HERE
<< "credentials state is '" << user()->credentials() << "'");
128 const char *keyExtras
= helperRequestKeyExtras(request
, al
);
129 if (user()->credentials() == Auth::Pending
) {
131 snprintf(buf
, sizeof(buf
), "YR %s %s\n", client_blob
, keyExtras
);
133 snprintf(buf
, sizeof(buf
), "YR %s\n", client_blob
); //CHECKME: can ever client_blob be 0 here?
136 snprintf(buf
, sizeof(buf
), "KK %s %s\n", client_blob
, keyExtras
);
138 snprintf(buf
, sizeof(buf
), "KK %s\n", client_blob
);
143 safe_free(client_blob
);
145 helperStatefulSubmit(negotiateauthenticators
, buf
, Auth::Negotiate::UserRequest::HandleReply
,
146 new Auth::StateData(this, handler
, data
), authserver
);
150 * Atomic action: properly release the Negotiate auth helpers which may have been reserved
151 * for this request connections use.
154 Auth::Negotiate::UserRequest::releaseAuthServer()
157 debugs(29, 6, HERE
<< "releasing Negotiate auth server '" << authserver
<< "'");
158 helperStatefulReleaseServer(authserver
);
161 debugs(29, 6, HERE
<< "No Negotiate auth server to release.");
165 Auth::Negotiate::UserRequest::authenticate(HttpRequest
* aRequest
, ConnStateData
* conn
, http_hdr_type type
)
167 /* Check that we are in the client side, where we can generate
170 if (conn
== NULL
|| !cbdataReferenceValid(conn
)) {
171 user()->credentials(Auth::Failed
);
172 debugs(29, DBG_IMPORTANT
, "WARNING: Negotiate Authentication attempt to perform authentication without a connection!");
177 debugs(29, DBG_IMPORTANT
, "WARNING: Negotiate Authentication waiting for helper reply!");
182 debugs(29, 2, HERE
<< "need to challenge client '" << server_blob
<< "'!");
187 const char *proxy_auth
= aRequest
->header
.getStr(type
);
189 /* locate second word */
190 const char *blob
= proxy_auth
;
193 while (xisspace(*blob
) && *blob
)
196 while (!xisspace(*blob
) && *blob
)
199 while (xisspace(*blob
) && *blob
)
203 switch (user()->credentials()) {
205 case Auth::Unchecked
:
206 /* we've received a negotiate request. pass to a helper */
207 debugs(29, 9, HERE
<< "auth state negotiate none. Received blob: '" << proxy_auth
<< "'");
208 user()->credentials(Auth::Pending
);
209 safe_free(client_blob
);
210 client_blob
=xstrdup(blob
);
211 assert(conn
->getAuth() == NULL
);
212 conn
->setAuth(this, "new Negotiate handshake request");
214 HTTPMSGLOCK(request
);
218 debugs(29, DBG_IMPORTANT
, HERE
<< "need to ask helper");
221 case Auth::Handshake
:
222 /* we should have received a blob from the client. Hand it off to
224 safe_free(client_blob
);
225 client_blob
= xstrdup(blob
);
227 HTTPMSGUNLOCK(request
);
229 HTTPMSGLOCK(request
);
233 fatal("Auth::Negotiate::UserRequest::authenticate: unexpected auth state DONE! Report a bug to the squid developers.\n");
237 /* we've failed somewhere in authentication */
238 debugs(29, 9, HERE
<< "auth state negotiate failed. " << proxy_auth
);
244 Auth::Negotiate::UserRequest::HandleReply(void *data
, const Helper::Reply
&reply
)
246 Auth::StateData
*r
= static_cast<Auth::StateData
*>(data
);
248 debugs(29, 8, HERE
<< "helper: '" << reply
.whichServer
<< "' sent us reply=" << reply
);
250 if (!cbdataReferenceValid(r
->data
)) {
251 debugs(29, DBG_IMPORTANT
, "ERROR: Negotiate Authentication invalid callback data. helper '" << reply
.whichServer
<< "'.");
256 Auth::UserRequest::Pointer auth_user_request
= r
->auth_user_request
;
257 assert(auth_user_request
!= NULL
);
259 // add new helper kv-pair notes to the credentials object
260 // so that any transaction using those credentials can access them
261 auth_user_request
->user()->notes
.appendNewOnly(&reply
.notes
);
263 Auth::Negotiate::UserRequest
*lm_request
= dynamic_cast<Auth::Negotiate::UserRequest
*>(auth_user_request
.getRaw());
264 assert(lm_request
!= NULL
);
265 assert(lm_request
->waiting
);
267 lm_request
->waiting
= 0;
268 safe_free(lm_request
->client_blob
);
270 assert(auth_user_request
->user() != NULL
);
271 assert(auth_user_request
->user()->auth_type
== Auth::AUTH_NEGOTIATE
);
273 if (lm_request
->authserver
== NULL
)
274 lm_request
->authserver
= reply
.whichServer
.get(); // XXX: no locking?
276 assert(reply
.whichServer
== lm_request
->authserver
);
278 switch (reply
.result
) {
280 /* we have been given a blob to send to the client */
281 safe_free(lm_request
->server_blob
);
282 lm_request
->request
->flags
.mustKeepalive
= true;
283 if (lm_request
->request
->flags
.proxyKeepalive
) {
284 const char *tokenNote
= reply
.notes
.findFirst("token");
285 lm_request
->server_blob
= xstrdup(tokenNote
);
286 auth_user_request
->user()->credentials(Auth::Handshake
);
287 auth_user_request
->denyMessage("Authentication in progress");
288 debugs(29, 4, HERE
<< "Need to challenge the client with a server token: '" << tokenNote
<< "'");
290 auth_user_request
->user()->credentials(Auth::Failed
);
291 auth_user_request
->denyMessage("Negotiate authentication requires a persistent connection");
296 const char *userNote
= reply
.notes
.findFirst("user");
297 const char *tokenNote
= reply
.notes
.findFirst("token");
298 if (userNote
== NULL
|| tokenNote
== NULL
) {
299 // XXX: handle a success with no username better
301 fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply
.other().content());
305 /* we're finished, release the helper */
306 auth_user_request
->user()->username(userNote
);
307 auth_user_request
->denyMessage("Login successful");
308 safe_free(lm_request
->server_blob
);
309 lm_request
->server_blob
= xstrdup(tokenNote
);
310 lm_request
->releaseAuthServer();
312 /* connection is authenticated */
313 debugs(29, 4, HERE
<< "authenticated user " << auth_user_request
->user()->username());
314 /* see if this is an existing user with a different proxy_auth
316 AuthUserHashPointer
*usernamehash
= static_cast<AuthUserHashPointer
*>(hash_lookup(proxy_auth_username_cache
, auth_user_request
->user()->userKey()));
317 Auth::User::Pointer local_auth_user
= lm_request
->user();
318 while (usernamehash
&& (usernamehash
->user()->auth_type
!= Auth::AUTH_NEGOTIATE
||
319 strcmp(usernamehash
->user()->userKey(), auth_user_request
->user()->userKey()) != 0))
320 usernamehash
= static_cast<AuthUserHashPointer
*>(usernamehash
->next
);
322 /* we can't seamlessly recheck the username due to the
323 * challenge-response nature of the protocol.
324 * Just free the temporary auth_user after merging as
325 * much of it new state into the existing one as possible */
326 usernamehash
->user()->absorb(local_auth_user
);
327 /* from here on we are working with the original cached credentials. */
328 local_auth_user
= usernamehash
->user();
329 auth_user_request
->user(local_auth_user
);
331 /* store user in hash's */
332 local_auth_user
->addToNameCache();
334 /* set these to now because this is either a new login from an
335 * existing user or a new user */
336 local_auth_user
->expiretime
= current_time
.tv_sec
;
337 auth_user_request
->user()->credentials(Auth::Ok
);
338 debugs(29, 4, HERE
<< "Successfully validated user via Negotiate. Username '" << auth_user_request
->user()->username() << "'");
342 case Helper::Error
: {
343 const char *messageNote
= reply
.notes
.find("message");
344 const char *tokenNote
= reply
.notes
.findFirst("token");
346 /* authentication failure (wrong password, etc.) */
347 if (messageNote
!= NULL
)
348 auth_user_request
->denyMessage(messageNote
);
350 auth_user_request
->denyMessage("Negotiate Authentication denied with no reason given");
351 auth_user_request
->user()->credentials(Auth::Failed
);
352 safe_free(lm_request
->server_blob
);
353 if (tokenNote
!= NULL
)
354 lm_request
->server_blob
= xstrdup(tokenNote
);
355 lm_request
->releaseAuthServer();
356 debugs(29, 4, "Failed validating user via Negotiate. Result: " << reply
);
360 case Helper::Unknown
:
361 debugs(29, DBG_IMPORTANT
, "ERROR: Negotiate Authentication Helper '" << reply
.whichServer
<< "' crashed!.");
362 /* continue to the next case */
364 case Helper::BrokenHelper
: {
365 /* TODO kick off a refresh process. This can occur after a YR or after
366 * a KK. If after a YR release the helper and resubmit the request via
367 * Authenticate Negotiate start.
368 * If after a KK deny the user's request w/ 407 and mark the helper as
370 const char *errNote
= reply
.notes
.find("message");
371 if (reply
.result
== Helper::Unknown
)
372 auth_user_request
->denyMessage("Internal Error");
373 else if (errNote
!= NULL
)
374 auth_user_request
->denyMessage(errNote
);
376 auth_user_request
->denyMessage("Negotiate Authentication failed with no reason given");
377 auth_user_request
->user()->credentials(Auth::Failed
);
378 safe_free(lm_request
->server_blob
);
379 lm_request
->releaseAuthServer();
380 debugs(29, DBG_IMPORTANT
, "ERROR: Negotiate Authentication validating user. Result: " << reply
);
384 if (lm_request
->request
) {
385 HTTPMSGUNLOCK(lm_request
->request
);
386 lm_request
->request
= NULL
;
393 Auth::Negotiate::UserRequest::addAuthenticationInfoHeader(HttpReply
* rep
, int accel
)
400 /* don't add to authentication error pages */
401 if ((!accel
&& rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
402 || (accel
&& rep
->sline
.status() == Http::scUnauthorized
))
405 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
406 httpHeaderPutStrf(&rep
->header
, type
, "Negotiate %s", server_blob
);
408 safe_free(server_blob
);